多线程,多进程和携程(实战篇)

797 阅读4分钟

实战篇二

线程之间通信的方法

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中的Queuesemaphore的实现都使用了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
      
    • 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()顺序会导致阻塞,程序无法继续进行。 执行过程如下

image.png

信号量(semaphore)

  • 用于控制进入数量的锁
  • 文件的读写,通常情况下读可以由多个线程来读,而写仅由一个线程来写
  • threading.semaphore(n)可以同时允许n个线程执行,大于n的部分必须等之前有线程释放后才能加入继续执行