要实现线程间通信,可以使用Python提供的多种机制,如队列、事件、信号量等。
队列
队列是多线程中最常用的通信方式。Python内置的queue
库提供了多种队列类型,如Queue
、LifoQueue
和PriorityQueue
等。其中,最常用的是Queue
队列类型。
Queue
对象是多个线程之间的通信工具,当一个线程把数据放进队列的时候,另外一个线程可以从队列中取出数据,从而实现线程间通信。
以下是一个使用Queue
队列实现线程间通信的示例:
import threading
import queue
def worker(q):
while True:
item = q.get()
if item is None:
break
print(item)
q.task_done()
q = queue.Queue()
for i in range(4):
t = threading.Thread(target=worker, args=(q,))
t.start()
for item in range(10):
q.put(item)
q.join()
for i in range(4):
q.put(None)
for t in threads:
t.join()
在上述示例中,worker()
函数中的while
循环一直运行,先调用q.get()
方法从队列中获取一条数据,然后打印这条数据。q.task_done()
方法则告诉队列这个任务已经完成。这个方法必须要在每一次获取到队列中的数据后调用。
在主线程中,首先创建了一个Queue
队列,然后创建了4个线程来执行worker()
方法。接着,主线程往队列中放入10条数据,并调用q.join()
方法等待队列中所有任务完成。最后,主线程往队列中放入4个None
作为结束标志,等待所有线程执行完毕。
事件
事件(event)是另外一种多线程中的通信机制。它实现的是“一个线程向其他线程发出信号”的模式。当事件对象的状态为真时,等待事件的线程会被唤醒。
在Python中,可以使用threading
模块中的Event
类来创建事件。
以下是一个使用Event
实现线程间通信的示例:
import threading
event = threading.Event()
def worker():
print('Waiting for event to trigger...')
event.wait()
print('Starting...')
threads= []
for i in range(4):
threads.append(threading.Thread(target=worker))
for t in threads:
t.start()
event.set()
for t in threads:
t.join()
在这个示例中,我们首先创建了一个Event
对象,并创建了4个线程来执行worker()
方法。在worker()
方法中,线程首先调用event.wait()
方法等待事件的触发,然后打印“Starting...”。
在主线程中,我们首先往事件中设置了一个标志,这使得所有的线程都被唤醒,执行完毕。
线程锁
在多线程中,如果多个线程同时访问共享资源,就有可能导致数据不一致。例如,在两个线程同时对同一个变量进行加1操作时,由于线程调度的不确定性,并不能保证每个线程都把这个变量加1,最终的结果就无法预知。
这时候,我们可以使用线程锁(Lock)来避免这种情况的发生。Python提供了threading
模块中的Lock
类来实现线程锁。
以下是一个使用线程锁实现线程间通信的示例:
import threading
count = 0
lock = threading.Lock()
def worker():
global count
for i in range(10):
lock.acquire()
count += 1
lock.release()
threads = []
for i in range(4):
threads.append(threading.Thread(target=worker))
for t in threads:
t.start()
for t in threads:
t.join()
print('Final value of count is:', count)
在这个示例中,我们首先创建了一个Lock
对象,并创建了4个线程来执行worker()
方法。在worker()
方法中,我们使用lock.acquire()
方法获取锁,执行修改共享资源的操作,再使用lock.release()
方法释放锁。
这个过程中,同一时刻只有一个线程可以获取到锁,执行修改共享资源的操作,从而避免了数据不一致的情况。
需要注意的是,在使用Lock时,一定要避免死锁(Deadlock)的问题。当多个线程相互等待对方释放锁时,就可能会出现死锁情况。为了避免这种情况,可以考虑使用Rlock
(可重入锁),这种锁可以被同一个线程多次获取,而不会出现死锁的情况。