python使用threading.Condition交替打印两个字符


Posted in Python onMay 07, 2019

Python中使用threading.Condition交替打印两个字符的程序。

这个程序涉及到两个线程的的协调问题,两个线程为了能够相互协调运行,必须持有一个共同的状态,通过这个状态来维护两个线程的执行,通过使用threading.Condition对象就能够完成两个线程之间的这种协调工作。

threading.Condition默认情况下会通过持有一个ReentrantLock来协调线程之间的工作,所谓可重入锁,是只一个可以由一个线程递归获取的锁,此锁对象会维护当前锁的所有者(线程)和当前所有者递归获取锁的次数(本文在逻辑上和可重入锁没有任何关系,完全可以用一个普通锁替代)。

Python文档中给出的描述是:它是一个与某个锁相联系的变量。同时它实现了上下文管理协议。其对象中除了acquire和release方法之外,其它方法的调用的前提是,当前线程必须是这个锁的所有者。

通过代码和其中的注释,能够非常明白地弄清楚Condition的原理是怎样的:

import threading
import time
import functools
 
 
def worker(cond, name):
 """worker running in different thread"""
 with cond: # 通过__enter__方法,获取cond对象中的锁,默认是一个ReentrantLock对象
  print('...{}-{}-{}'.format(name, threading.current_thread().getName(), cond._is_owned()))
  cond.wait() # 创建一个新的锁NEWLOCK,调用acquire将NEWLOCK获取,然后将NEWLOCK放入等待列表中,\
  # 释放cond._lock锁(_release_save),最后再次调用acquire让NEWLOCK阻塞
 print('wait returned in {}'.format(name))
 
 
if __name__ == '__main__':
 condition = threading.Condition()
 t1 = threading.Thread(target=functools.partial(worker, condition, 't1'))
 t2 = threading.Thread(target=functools.partial(worker, condition, 't2'))
 
 t2.start() # 启动线程2
 t1.start() # 启动线程1
 
 time.sleep(2)
 with condition:
  condition.notify(1) # 按照FIFO顺序(wait调用顺序),释放一个锁,并将其从等待列表中删除
 
 time.sleep(2)
 
 with condition:
  condition.notify(1) # 按照FIFO顺序(wait调用顺序),释放另一个锁,并将其从等待队列中删除
 
 t1.join() # 主线程等待子线程结束
 t2.join() # 主线程等待子线程结束
 
 print('All done')

其输出为:

...t2-Thread-2-True
...t1-Thread-1-True
wait returned in t2
wait returned in t1
All done

其中wait方法要求获取到threading.Condition对象中的锁(如果没有提供,默认使用一个可重入锁),然后自己创建一个新的普通锁(NEWLOCK),并获取这个NEWLOCK;之后调用_release_save方法释放threading.Condition对象中的锁,让其它线程能够获取到;最后再次调用NEWLOCK上的acquire方法,由于在创建时已经acquire过,所以此线程会阻塞在此。而wait想要继续执行,必须等待其它线程将产生阻塞的这个NEWLOCK给release掉,当然,这就是notify方法的责任了。

notify方法接收一个数字n,从等待列表中取出相应数量的等待对象(让wait方法阻塞的锁对象),调用其release方法,让对应的wait方法能够返回。而notify_all方法仅仅就是将n设置为等待列表的总长度而已。

在理解了threading.Condition对象中wait和notify的工作原理之后,我们就可以利用它们来实现两个线程交替打印字符的功能了:

import threading
import functools
import time
 
 
def print_a(state):
 while True:
  if state.closed:
   print('Close a')
   return
  print('A')
  time.sleep(2)
  state.set_current_is_a(True)
  state.wait_for_b()
 
 
def print_b(state):
 while True:
  if state.closed:
   print('Close b')
   return
  state.wait_for_a()
  print('B')
  time.sleep(2)
  state.set_current_is_a(False)
 
 
if __name__ == '__main__':
 class State(object):
  """state used to coordinate multiple(two here) threads"""
  def __init__(self):
   self.condition = threading.Condition()
   self.current_is_a = False
   self.closed = False
 
  def wait_for_a(self):
   with self.condition:
    while not self.current_is_a:
     self.condition.wait()
 
  def wait_for_b(self):
   with self.condition:
    while self.current_is_a:
     self.condition.wait()
 
  def set_current_is_a(self, flag):
   self.current_is_a = flag
   with self.condition:
    self.condition.notify_all()
 
 
 state = State()
 t1 = threading.Thread(target=functools.partial(print_a, state))
 t2 = threading.Thread(target=functools.partial(print_b, state))
 
 try:
  t1.start()
  t2.start()
 
  t1.join()
  t2.join()
 except KeyboardInterrupt:
  state.closed = True
  print('Closed')

可以看到有两种类型的任务,一个用于打印字符A,一个用于打印字符B,我们的实现种让A先于B打印,所以在print_a中,先打印A,再设置当前字符状态并释放等待列表中的所有锁(set_current_is_a),如果没有这一步,current_is_a将一直是False,wait_for_b能够返回,而wait_for_a却永远不会返回,最终效果就是每隔两秒就打印一个字符A,而B永远不会打印。另一个副作用是如果wait_for_a永远不会返回,那print_b所在线程的关闭逻辑也就无法执行,最终会成为僵尸线程(这里的关闭逻辑只用作示例,生产环境需要更加完善的关闭机制)。

考虑另一种情况,print_a种将set_current_is_a和wait_for_b交换一下位置会怎么样。从观察到的输出我们看到,程序首先输出了一个字符A,以后,每隔2秒钟,就会同时输出A和B,而不是交替输出。原因在于,由于current_is_a还是False,我们先调用的wait_for_b其会立即返回,之后调用set_current_is_a,将current_is_a设置为True,并释放所有的阻塞wait的锁(notify_all),这个过程中没有阻塞,print_a紧接着进入了下一个打印循环;与此同时,print_b中的wait_for_a也返回了,进入到B的打印循环,故最终我们看到A和B总是一起打印。

可见对于threading.Condition的使用需要多加小心,要注意逻辑上的严谨性。

附一个队列版本:

import threading
import functools
import time
from queue import Queue
 
 
def print_a(q_a, q_b):
 while True:
  char_a = q_a.get()
  if char_a == 'closed':
   return
  print(char_a)
  time.sleep(2)
  q_b.put('B')
 
 
def print_b(q_a, q_b):
 while True:
  char_b = q_b.get()
  if char_b == 'closed':
   return
  print(char_b)
  time.sleep(2)
  q_a.put('A')
 
 
if __name__ == '__main__':
 q_a = Queue()
 q_b = Queue()
 
 t1 = threading.Thread(target=functools.partial(print_a, q_a, q_b))
 t2 = threading.Thread(target=functools.partial(print_b, q_a, q_b))
 
 try:
  t1.start()
  t2.start()
 
  q_a.put('A')
 
  t1.join()
  t2.join()
 except KeyboardInterrupt:
  q_a.put('closed')
  q_b.put('closed')
 
 print('Done')

队列版本逻辑更清晰,更不容易出错,实际应用中应该选用队列。 

附一个协程版本(Python 3.5+):

import time
import asyncio
 
 
async def print_a():
 while True:
  print('a')
  time.sleep(2) # simulate the CPU block time
  await asyncio.sleep(0) # release control to event loop
 
 
async def print_b():
 while True:
  print('b')
  time.sleep(2) # simulate the CPU block time
  await asyncio.sleep(0) # release control to event loop
 
 
async def main():
 await asyncio.wait([print_a(), print_b()])
 
 
if __name__ == '__main__':
 loop = asyncio.get_event_loop()
 loop.run_until_complete(main())

协程的运行需要依附于一个事件循环(select/poll/epoll/kqueue),通过async def将一个函数定义为协程,通过await主动让渡控制权,通过相互让渡控制权完成交替打印字符。整个程序运行于一个线程中,这样就没有线程间协调的工作,仅仅是控制权的让渡逻辑。对于IO密集型操作,而没有明显的CPU阻塞(计算复杂,以致出现明显的延时,比如复杂加解密算法)的情况下非常合适。

附一个Java版本:

PrintMain类,用于管理和协调打印A和打印B的两个线程:

package com.cuttyfox.tests.self.version1;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
 
public class PrintMain {
 private boolean currentIsA = false;
 
 public synchronized void waitingForPrintingA() throws InterruptedException {
  while (this.currentIsA == false) {
   wait();
  }
 }
 
 public synchronized void waitingForPrintingB() throws InterruptedException {
  while (this.currentIsA == true) {
   wait();
  }
 }
 
 public synchronized void setCurrentIsA(boolean flag) {
  this.currentIsA = flag;
  notifyAll();
 }
 
 public static void main(String[] args) throws Exception {
  PrintMain state = new PrintMain();
  ExecutorService executorService = Executors.newCachedThreadPool();
  executorService.execute(new PrintB(state));
  executorService.execute(new PrintA(state));
  executorService.shutdown();
  executorService.awaitTermination(10, TimeUnit.SECONDS);
  System.out.println("Done");
  System.exit(0);
 }
}

打印A的线程(首先打印A):

package com.cuttyfox.tests.self.version1;
 
import java.util.concurrent.TimeUnit;
 
public class PrintA implements Runnable{
 private PrintMain state;
 
 public PrintA(PrintMain state) {
  this.state = state;
 }
 
 public void run() {
  try {
   while (!Thread.interrupted()){
    System.out.println("Print A");
    TimeUnit.SECONDS.sleep(1);
    this.state.setCurrentIsA(true);
    this.state.waitingForPrintingB();
   }
  } catch (InterruptedException e) {
   System.out.println("Exit through Interrupting.");
  }
 
 }
}

打印B的线程:

package com.cuttyfox.tests.self.version1;
 
import java.util.concurrent.TimeUnit;
 
public class PrintB implements Runnable{
 private PrintMain state;
 
 public PrintB(PrintMain state) {
  this.state = state;
 }
 
 public void run() {
  try{
   while (!Thread.interrupted()) {
    this.state.waitingForPrintingA();
    System.out.println("Print B");
    TimeUnit.SECONDS.sleep(1);
    this.state.setCurrentIsA(false);
   }
  } catch (InterruptedException e) {
   System.out.println("Exit through Interrupting.");
  }
 
 }
}

Java对象本身有对象锁,故这里没有像Python中那样需要显式通过创建一个Condition对象来得到一把锁。

使用Python实现交替打印abcdef的过程:

import threading
import time
import functools
from collections import deque
 
 LETTERS = [chr(code) for code in range(97, 97+6)]
 LENGTH = len(LETTERS)
 
 
 class State(object):
  def __init__(self):
   self.condition = threading.Condition()
   self.index_value = 0
 
  def set_next_index(self, index):
   with self.condition:
    self.index_value = index
    self.condition.notify_all()
 
  def wait_for(self, index_value):
   with self.condition:
    while not self.index_value == index_value:
     self.condition.wait()
 
 
 def print_letter(state: State, wait_ident: int):
  print('Got: {}!'.format(wait_ident))
  while True:
   state.wait_for(wait_ident)
   time.sleep(2)
   print(LETTERS[state.index_value])
   print('PRINT: {} AND SET NEXT: {}'.format(state.index_value,
              (state.index_value + 1) % LENGTH
              ))
   state.set_next_index((state.index_value + 1) % LENGTH)
 
 
 state = State()
 d = deque()
 d.extend(range(LENGTH))
 d.rotate(1)
 print(d)
 
 threads = []
 for wait_ident in d:
  t = threading.Thread(target=functools.partial(print_letter, state, wait_ident))
  threads.append(t)
 
 for thread in threads:
  thread.start()
 
 for thread in threads:
  thread.join()

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python开启多个子进程并行运行的方法
Apr 18 Python
python正则中最短匹配实现代码
Jan 16 Python
python3 破解 geetest(极验)的滑块验证码功能
Feb 24 Python
python八皇后问题的解决方法
Sep 27 Python
python脚本执行CMD命令并返回结果的例子
Aug 14 Python
从pandas一个单元格的字符串中提取字符串方式
Dec 17 Python
Python3-异步进程回调函数(callback())介绍
May 02 Python
Python 判断时间是否在时间区间内的实例
May 16 Python
Python爬虫爬取百度搜索内容代码实例
Jun 05 Python
Python 如何实现访问者模式
Jul 28 Python
python查询MySQL将数据写入Excel
Oct 29 Python
python归并排序算法过程实例讲解
Nov 04 Python
python实现银联支付和支付宝支付接入
May 07 #Python
详解Python 爬取13个旅游城市,告诉你五一大家最爱去哪玩?
May 07 #Python
Scrapy-Redis结合POST请求获取数据的方法示例
May 07 #Python
Python数据类型之Set集合实例详解
May 07 #Python
Python数据类型之Dict字典实例详解
May 07 #Python
Python分支语句与循环语句应用实例分析
May 07 #Python
Python正则表达式实现简易计算器功能示例
May 07 #Python
You might like
透析PHP的配置文件php.ini
2006/10/09 PHP
Dedecms常用函数解析
2008/02/01 PHP
php获取mysql数据库中的所有表名的代码
2011/04/23 PHP
php中常用字符串处理代码片段整理
2011/11/07 PHP
浅析关于PHP位运算的简单权限设计
2013/06/30 PHP
php设计模式之命令模式使用示例
2014/03/02 PHP
分享一段php获取linux服务器状态的代码
2014/05/27 PHP
支持中文的PHP按字符串长度分割成数组代码
2015/05/17 PHP
浅谈PHP无限极分类原理
2019/03/14 PHP
在TP5数据库中四个字段实现无限分类的示例
2019/10/18 PHP
php设计模式之职责链模式实例分析【星际争霸游戏案例】
2020/03/27 PHP
jquery blockUI 遮罩不能消失与不能提交的解决方法
2011/09/17 Javascript
使用JavaScript动态设置样式实现代码(2)
2013/01/25 Javascript
原生JS实现加入收藏夹的代码
2013/10/24 Javascript
使用jQuery解决IE与FireFox下createElement方法的差异
2013/11/14 Javascript
jQuery实现Twitter的自动文字补齐特效
2014/11/28 Javascript
webpack开发跨域问题解决办法
2017/08/03 Javascript
vue注册组件的几种方式总结
2018/03/08 Javascript
vue 每次渲染完页面后div的滚动条保持在最底部的方法
2018/03/17 Javascript
微信小程序外卖选购页实现切换分类与数量加减功能案例
2019/01/15 Javascript
jquery制作的移动端购物车效果完整示例
2020/02/24 jQuery
Python 字符串定义
2009/09/25 Python
python搭建简易服务器分析与实现
2012/12/15 Python
用Python编写脚本使IE实现代理上网的教程
2015/04/23 Python
python3实现磁盘空间监控
2018/06/21 Python
Python中将两个或多个list合成一个list的方法小结
2019/05/12 Python
HTML5中的拖放实现详解
2017/08/23 HTML / CSS
FOREO斐珞尔官方旗舰店:LUNA露娜洁面仪
2018/03/11 全球购物
《鱼游到了纸上》教学反思
2014/02/20 职场文书
超市客服工作职责
2014/06/11 职场文书
大学生撤销处分思想汇报
2014/09/12 职场文书
“四风”问题的主要表现和危害思想汇报
2014/09/19 职场文书
公司领导班子召开党的群众路线教育实践活动总结大会新闻稿
2014/10/21 职场文书
Python中的 Set 与 dict
2022/03/13 Python
AngularJS实现多级下拉框
2022/03/25 Javascript
Python matplotlib绘制雷达图
2022/04/13 Python