跳转至

Python线程锁

正常上锁的情况

三个线程对num进行增减,对num操作部分进行上锁,进入锁之后,线程串行运行

# -*- coding: utf-8 -*-
import time, threading, ctypes

num = 0
lock = threading.Lock()


def task_thread(n):
    global num
    print("-----开始线程时间" + time.asctime(time.localtime(time.time())))
    lock.acquire()
    print("进入线程锁时间" + time.asctime(time.localtime(time.time())))
    for i in range(10000000):
        num = num + n
        num = num - n
    lock.release()


t1 = threading.Thread(target=task_thread, args=(6,))
t2 = threading.Thread(target=task_thread, args=(17,))
t3 = threading.Thread(target=task_thread, args=(11,))

t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
print(num)

运行结果:

-----开始线程时间Tue Aug 23 19:29:29 2022
进入线程锁时间Tue Aug 23 19:29:29 2022
-----开始线程时间Tue Aug 23 19:29:29 2022
-----开始线程时间Tue Aug 23 19:29:29 2022
进入线程锁时间Tue Aug 23 19:29:31 2022
进入线程锁时间Tue Aug 23 19:29:32 2022
0

不上锁的情况

去掉线程锁后,线程都开始同时进行操作,算出来的数字也是错误的

# -*- coding: utf-8 -*-
import time, threading, ctypes

num = 0


def task_thread(n):
    global num
    print("-----开始线程时间" + time.asctime(time.localtime(time.time())))
    print("进入线程时间" + time.asctime(time.localtime(time.time())))
    for i in range(10000000):
        num = num + n
        num = num - n


t1 = threading.Thread(target=task_thread, args=(6,))
t2 = threading.Thread(target=task_thread, args=(17,))
t3 = threading.Thread(target=task_thread, args=(11,))

t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
print(num)

运行结果:

-----开始线程时间Tue Aug 23 19:37:09 2022
进入线程时间Tue Aug 23 19:37:09 2022
-----开始线程时间Tue Aug 23 19:37:09 2022
进入线程时间Tue Aug 23 19:37:09 2022
-----开始线程时间Tue Aug 23 19:37:09 2022
进入线程时间Tue Aug 23 19:37:09 2022
10

Python 中的线程锁threading.Lock)是一种用于在多线程环境下实现线程同步的工具。它可以防止多个线程同时访问共享资源,避免数据竞争或数据不一致的问题。


1. 线程锁的定义

线程锁是一种互斥锁,也叫 mutex(mutual exclusion),它的工作方式是: - 加锁(Lock):当一个线程需要访问共享资源时,首先尝试获取锁。如果锁已经被其他线程占用,该线程将阻塞等待,直到锁被释放。 - 解锁(Unlock):线程完成对共享资源的操作后,释放锁,其他阻塞的线程才能继续执行。


2. 线程锁的用法

Python 提供了 threading 模块中的 Lock 类,可以用来创建和使用线程锁。

示例代码

import threading
import time

# 定义共享资源
shared_resource = 0

# 创建线程锁
lock = threading.Lock()

def thread_task():
    global shared_resource
    for _ in range(1000000):
        # 加锁
        lock.acquire()
        try:
            shared_resource += 1
        finally:
            # 解锁
            lock.release()

# 创建两个线程
thread1 = threading.Thread(target=thread_task)
thread2 = threading.Thread(target=thread_task)

# 启动线程
thread1.start()
thread2.start()

# 等待线程结束
thread1.join()
thread2.join()

print("Final value of shared_resource:", shared_resource)

运行结果:

  • 在未使用锁的情况下,shared_resource 的值可能小于预期,因为多个线程可能同时访问共享变量。
  • 使用锁后,能够确保共享变量的操作是原子性的,从而避免数据竞争。

3. 常用方法

1. lock.acquire(blocking=True, timeout=-1)

  • 功能: 获取锁。
  • 参数:
  • blocking
    • True(默认):阻塞模式,等待直到获取到锁。
    • False:非阻塞模式,如果无法立即获取锁,返回 False
  • timeout:阻塞模式下,设置最大等待时间(秒)。如果超时未获取锁,则返回 False
  • 返回值: 是否成功获取锁。
if lock.acquire(timeout=2):  # 尝试获取锁,最多等待2秒
    try:
        # 处理逻辑
        pass
    finally:
        lock.release()
else:
    print("Failed to acquire lock")

2. lock.release()

  • 功能: 释放锁。
  • 注意: 必须在持有锁的线程中调用,否则会抛出异常。

4. 线程锁的意义

  1. 避免数据竞争: 在多线程访问共享资源时,可能出现多个线程同时修改数据的情况,导致数据不一致或错误。

  2. 保证操作原子性: 锁确保某一时刻只有一个线程可以执行特定代码块,从而实现操作的不可分割性。

  3. 提高程序可靠性: 使用锁能有效避免程序因并发访问而出现的潜在问题。


5. 线程锁的注意事项

  1. 死锁问题: 如果多个线程互相等待对方释放锁,就会出现死锁。例如:
    lock1 = threading.Lock()
    lock2 = threading.Lock()
    
    def thread1():
        lock1.acquire()
        lock2.acquire()  # 如果 lock2 已被线程2占用,可能会死锁
    
    def thread2():
        lock2.acquire()
        lock1.acquire()  # 如果 lock1 已被线程1占用,可能会死锁

解决方法: - 避免嵌套使用多个锁。 - 使用 threading.RLock(可重入锁)。

  1. 性能损耗:
  2. 使用锁会增加线程之间的切换时间,影响性能。
  3. 避免频繁锁定和解锁,尽量缩小加锁代码块的范围。

  4. 推荐使用 with 语句:

  5. 可以简化锁的获取和释放逻辑,避免遗漏解锁操作。

示例:

with lock:
    # 此处加锁,确保安全操作
    shared_resource += 1
# 自动释放锁


6. 高级用法

除了 threading.Lock,Python 还提供了一些其他同步工具: - threading.RLock(可重入锁):
允许同一线程多次获取锁,用于嵌套的锁定逻辑。

  • threading.Semaphore(信号量):
    用于控制对某些资源的访问线程数量。

  • threading.Condition(条件变量):
    配合锁使用,用于线程间的协调和通信。

  • threading.Event
    实现线程之间的简单信号通知机制。


总结

线程锁是 Python 中实现线程同步的关键工具,通过 threading.Lock 类可以轻松加锁和解锁,确保共享资源在多线程环境下的安全性。但需要注意使用锁可能带来的死锁和性能问题,合理使用同步工具是实现并发编程的关键。