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. 线程锁的意义
-
避免数据竞争: 在多线程访问共享资源时,可能出现多个线程同时修改数据的情况,导致数据不一致或错误。
-
保证操作原子性: 锁确保某一时刻只有一个线程可以执行特定代码块,从而实现操作的不可分割性。
-
提高程序可靠性: 使用锁能有效避免程序因并发访问而出现的潜在问题。
5. 线程锁的注意事项
- 死锁问题:
如果多个线程互相等待对方释放锁,就会出现死锁。例如:
lock1 = threading.Lock() lock2 = threading.Lock() def thread1(): lock1.acquire() lock2.acquire() # 如果 lock2 已被线程2占用,可能会死锁 def thread2(): lock2.acquire() lock1.acquire() # 如果 lock1 已被线程1占用,可能会死锁
解决方法:
- 避免嵌套使用多个锁。
- 使用 threading.RLock
(可重入锁)。
- 性能损耗:
- 使用锁会增加线程之间的切换时间,影响性能。
-
避免频繁锁定和解锁,尽量缩小加锁代码块的范围。
-
推荐使用
with
语句: - 可以简化锁的获取和释放逻辑,避免遗漏解锁操作。
示例:
with lock:
# 此处加锁,确保安全操作
shared_resource += 1
# 自动释放锁
6. 高级用法
除了 threading.Lock
,Python 还提供了一些其他同步工具:
- threading.RLock
(可重入锁):
允许同一线程多次获取锁,用于嵌套的锁定逻辑。
-
threading.Semaphore
(信号量):
用于控制对某些资源的访问线程数量。 -
threading.Condition
(条件变量):
配合锁使用,用于线程间的协调和通信。 -
threading.Event
:
实现线程之间的简单信号通知机制。
总结
线程锁是 Python 中实现线程同步的关键工具,通过 threading.Lock
类可以轻松加锁和解锁,确保共享资源在多线程环境下的安全性。但需要注意使用锁可能带来的死锁和性能问题,合理使用同步工具是实现并发编程的关键。