代码实现线程安全的动态更新
import threading
shared_dict = {}
lock = threading.Lock()
def update_dict(key, value):
with lock:
shared_dict[key] = value
# 模拟多线程更新
threads = []
for i in range(10):
t = threading.Thread(target=update_dict, args=(i, i * 2))
threads.append(t)
t.start()
for t in threads:
t.join()
print(shared_dict)
Lock
、RLock
、Semaphore
适用情况及区别
Lock
(互斥锁):
- 适用情况:适用于简单的互斥场景,即同一时间只允许一个线程访问共享资源。在更新共享字典的场景中,使用
Lock
可以确保每次只有一个线程能对字典进行更新操作,避免数据竞争。
- 特点:当一个线程获取了
Lock
,其他线程必须等待该线程释放锁后才能获取。如果一个线程尝试获取已经被其他线程持有的Lock
,它将被阻塞,直到锁被释放。
RLock
(可重入锁):
- 适用情况:当一个线程可能需要多次获取锁时使用。例如,在一个递归函数中,如果函数内部需要访问共享字典,且函数可能被递归调用,使用
RLock
可以避免死锁。因为RLock
允许同一个线程多次获取锁,每次获取锁会增加锁的计数,每次释放锁会减少计数,当计数为0时,锁才真正被释放。
- 区别于
Lock
:Lock
如果被同一个线程多次获取会导致死锁,而RLock
不会。
Semaphore
(信号量):
- 适用情况:如果希望控制同时访问共享字典的线程数量,
Semaphore
就很有用。例如,只允许最多3个线程同时更新共享字典,可以创建一个初始值为3的Semaphore
。每个线程在访问共享字典前获取信号量,访问结束后释放信号量。
- 区别于
Lock
和RLock
:Lock
和RLock
本质上是二元信号量(值为1),而Semaphore
可以设置大于1的值,允许多个线程同时访问共享资源,只是数量受信号量值的限制。
GIL(全局解释器锁)对这种场景的影响
- GIL的作用:GIL是Python解释器中的一个机制,它确保在任何时刻,只有一个线程能在Python解释器中执行字节码。这意味着在CPU密集型任务中,多线程并不能利用多核CPU的优势,因为同一时间只有一个线程能执行。
- 对共享字典更新场景的影响:虽然GIL保证了同一时间只有一个线程在执行Python字节码,但对于共享字典的更新操作,仅仅依靠GIL是不够的。因为更新字典涉及到多个字节码操作(如读取字典、修改字典、写入字典等),在执行这些操作的过程中,GIL可能会被释放(例如在执行I/O操作或长时间的计算时),其他线程就可能在这个间隙访问共享字典,导致数据竞争。所以,即使有GIL,仍然需要使用同步原语(如
Lock
、RLock
、Semaphore
)来保证共享字典更新的线程安全性。