前言
在多线程编程中,同步原语(synchronization primitives)是确保线程安全和正确执行的关键。Python 提供了一些基础的同步原语,用于协调线程之间的访问和操作。Python的同步原语包括 Lock、RLock、Semaphore、Event 和 Condition,接下来简单介绍一下他们的使用
Lock
Lock(锁)是最简单也是大家最熟悉的同步原语,相信所有的编程语言中,大伙都接触过并且使用过。它允许一个线程独占对共享资源的访问,防止其他线程在同一时间访问该资源。
import threading
lock = threading.Lock()
count = 0
def add():
for _ in range(10000):
global count,lock
lock.acquire()
count += 1
lock.release()
threads = []
for _ in range(100):
thread = threading.Thread(target=add)
threads.append(thread)
thread.start()
for t in threads:
t.join()
print(count)
在上面代码中,我们对一个变量进行加一操作,然后在100各线程中执行,每个线程执行操作时,先获取锁,执行之后在释放锁,这样就避免了竞态条件,确保了最终的计数值是正确的。在测试中你可以去掉锁试试,多测试几次,看是不是每次都能获得到预想的结果。
RLock
RLock(可重入锁)是 Lock 的扩展,它允许同一线程多次获得锁,而不会导致死锁。
import threading
rlock = threading.RLock()
def rlock_test():
with rlock:
print("Thread", threading.current_thread().name, "acquired the lock")
with rlock:
print("Thread", threading.current_thread().name, "acquired the lock again")
threads = [threading.Thread(target=rlock_test) for i in range(5)]
for t in threads:
t.start()
for t in threads:
t.join()
如果你运行了上面代码,你可以发现所有线程都正常执行了,说明在执行rlock_test()函数时,并没有进入死锁状态,你可以奖RLock换成Lock试试。
Semaphore
Semaphore(信号量)允许多个线程同时访问共享资源。它维护一个计数器,只有在计数器大于零时,线程才能访问资源,并在访问后递减计数器。 信号量在所有语言中,都是一个重要同步原语。
import threading
import time
semaphore = threading.Semaphore(3)
def semaphore_test():
with semaphore:
print("Thread", threading.current_thread().name, "is accessing the resource\n")
# 模拟资源访问时间
time.sleep(1)
threads = [threading.Thread(target=semaphore_test) for _ in range(12)]
t1 = time.time()
for t in threads:
t.start()
for t in threads:
t.join()
interval = time.time() - t1
print(interval)
我们把Semaphore初始化为3,然后执行12个线程,在我的电脑中执行完差不多4s,如果换成了Lock锁,执行时间是12s多,你可以好好理解一下,是不是差不多4个批次?一个批次执行了3个线程?这就是上面说的,信号量允许多个线程同时访问共享资源,记住这一点很重要。
Event
Event(事件)用于线程间通信。一个线程可以设置事件,其他线程可以等待事件的发生。比如在开发中,可能需要等待某个线程完成后,再去执行其他的线程,这个时候可以使用Event
import threading
import time
event = threading.Event()
def wait_for_event():
print(threading.current_thread().name, "is waiting for the event\n")
event.wait()
print(threading.current_thread().name, "got the event\n")
def trigger_event():
print(threading.current_thread().name, "setting the event\n")
event.set()
threads = [threading.Thread(target=wait_for_event) for _ in range(5)]
trigger_thread = threading.Thread(target=trigger_event)
for t in threads:
t.start()
time.sleep(10)
trigger_thread.start()
for t in threads:
t.join()
trigger_thread.join()
运行上面代码,你可以看到有5个线程打印出了第一个函数中的第一个print,等待trigger_thread执行后,才会继续执行完函数。
Condition
Condition(条件变量)用于复杂的线程同步场景,它通常与锁一起使用,以便线程可以等待某些条件满足。
import threading
import time
condition = threading.Condition()
def consumer(i):
with condition:
print(f'Consumer{i} is waiting')
condition.wait()
print(f"Consumer{i} got the signal")
def producer():
with condition:
print("Producer is setting the condition")
time.sleep(10)
print("Producer notify all")
condition.notify_all()
consumer_threads = []
consumer_threads =[threading.Thread(target=consumer, args=(i,)) for i in range(5)]
producer_thread = threading.Thread(target=producer)
for t in consumer_threads:
t.start()
producer_thread.start()
for t in consumer_threads:
t.join()
producer_thread.join()
运行上面代码,可以看到如下结果
Consumer0 is waiting
Consumer1 is waiting
Consumer2 is waiting
Consumer3 is waiting
Consumer4 is waiting
Producer is setting the condition
Producer notify all
Consumer2 got the signal
Consumer4 got the signal
Consumer1 got the signal
Consumer3 got the signal
Consumer0 got the signal
consumer_threads 中的线程等待producer_thread执行**condition.notify_all()**后,才会继续执行。
总结
Python 和其他语言哟杨,同步原语为多线程编程提供了强大的工具,以确保线程安全和正确执行。通过理解和使用这些同步原语,开发者可以有效地避免竞态条件(race conditions)和死锁问题,从而编写出更加可靠和健壮的多线程应用程序。在实际开发中,可能80%的情景之下使用Lock就足够了,但其他的几种同步原语对象我们也要了解,在一些特定情况下对我们很有帮助