实战篇二
线程之间通信的方法
queue:
- queue是线程安全的,其源码中已经使用了锁
- queue也是共享变量的一种,只是它自身已经具有线程安全性。如果换成其他变量可能会出现不同步的问题。
- 常用方法:
from queue import Queue
put(self, item, block=True, timeout=None)
:将元素加入队列,队列为满时为阻塞,可以设定timeout参数get(self, block=True, timeout=None)
:从队列中取出元素,队列为空时阻塞,可以设定timeout参数qsize()
:返回队列的大小empty()
:判断是否为空full()
:判断是否为满join()
:阻塞直到队列中的所有元素都被取出执行。在调用该方法前要使用task_done()
方法,否则队列将一直处在阻塞状态task_done()
:指示之前的队列任务已经完成put_nowait(self, item)
:非阻塞模式,队列满时直接抛出异常get_nowait(self)
:非阻塞模式,队列空时直接抛出异常
condition
from threading import Condition
-
条件变量,用于复杂的线程间的同步。
-
threading中的
Queue
和semaphore
的实现都使用了condition -
Condition
中定义了__enter__
和__exit__
方法,所以可以使用with
语句实现上下文管理 -
常用方法:
-
Condition中实际上有两层锁:
- 初始化时会上一把默认的递归锁锁住condition,这把锁会在调用
wait()
方法时被解除,称为底层锁 - 在调用
wait()
方法时还会分配一把锁将当前进程锁住,并将这把锁添加到一个双端队列中去。等到调用notify()
方法时该锁会被释放掉
def __init__(self, lock=None): if lock is None: lock = RLock() self._lock = lock # Export the lock's acquire() and release() methods self.acquire = lock.acquire self.release = lock.release def wait(self, timeout=None): if not self._is_owned(): raise RuntimeError("cannot wait on un-acquired lock") waiter = _allocate_lock() waiter.acquire() # 分配一把锁锁住当前进程 self._waiters.append(waiter) # 将锁加入到一个双端队列中去 saved_state = self._release_save() # 释放掉初始化conditon时加上的底层锁 def notify(self, n=1): if not self._is_owned(): raise RuntimeError("cannot notify on un-acquired lock") all_waiters = self._waiters waiters_to_notify = _deque(_islice(all_waiters, n)) if not waiters_to_notify: return for waiter in waiters_to_notify: waiter.release() # 释放掉双端队列中的锁 try: all_waiters.remove(waiter) except ValueError: pass
- 初始化时会上一把默认的递归锁锁住condition,这把锁会在调用
-
wait(self, predicate, timeout=None)
:-
Wait until notified or until a timeout occurs.
This method releases the underlying lock, and then blocks until it is awakened by a notify() or notify_all() for the same condition variable in another thread, or until the optional timeout occurs.
-
释放掉当前线程的底层锁,使得其他线程可以访问当前线程。同时为当前线程加上一把锁,在收到
notify()
的通知之前阻塞当前线程剩余的部分继续执行。 -
即:在调用了
wait()
方法后,需要等待notify()
的通知,才能启动。
-
-
notify(self, n=1)
:-
Wake up one or more threads waiting on this condition.
-
释放掉当前线程的锁,使得其余线程的
wait()
方法可以获得锁
-
-
-
示例代码
class PersonA(threading.Thread): def __init__(self,cond): super().__init__(name="A") self.cond = cond def run(self): with self.cond: #为PersonA加上一把底层锁 print('{}: Hello B,你在吗?'.format(self.name)) self.cond.notify() #给B发出通知,释放掉B中wait加上的锁 self.cond.wait() #释放掉A的底层锁,确保下一次收到notify()通知后线程能够切入。同时给A加上一把锁,阻塞之后程序的运行 class PersonB(threading.Thread): def __init__(self,cond): super().__init__(name="B") self.cond = cond def run(self): with self.cond: #为PersonB加上一把底层锁 self.cond.wait() # 使用wait方法阻塞后续程序,需要等待A发出通知,同时释放掉B的底层锁,保证线程可以切入进来 print('{}: Hello A,我在'.format(self.name)) self.cond.notify() # 给A发出一个通知,同时释放掉A中wait加上的锁 if __name__ == '__main__': cond = threading.Condition() A = PersonA(cond) B = PersonB(cond) B.start() A.start() #如果先调用A.start(),程序会阻塞,无法继续进行,原因在于先启动A时,notify()方法已经被发出,而此时B的wait()方法还没有启动,也就没法接受到notify()的通知,所以程序无法再继续 >> A: Hello B,你在吗? B: Hello A,我在
- 注意
start()
的顺序很重要,错误的start()
顺序会导致阻塞,程序无法继续进行。 执行过程如下
- 注意
信号量(semaphore)
- 用于控制进入数量的锁
- 文件的读写,通常情况下读可以由多个线程来读,而写仅由一个线程来写
threading.semaphore(n)
:可以同时允许n个线程执行,大于n的部分必须等之前有线程释放后才能加入继续执行。