Python如何实现线程间通信


Posted in Python onJuly 30, 2020

问题

你的程序中有多个线程,你需要在这些线程之间安全地交换信息或数据

解决方案

从一个线程向另一个线程发送数据最安全的方式可能就是使用 queue 库中的队列了。创建一个被多个线程共享的 Queue 对象,这些线程通过使用 put() 和 get() 操作来向队列中添加或者删除元素。 例如:

from queue import Queue
from threading import Thread

# A thread that produces data
def producer(out_q):
  while True:
    # Produce some data
    ...
    out_q.put(data)

# A thread that consumes data
def consumer(in_q):
  while True:
# Get some data
    data = in_q.get()
    # Process the data
    ...

# Create the shared queue and launch both threads
q = Queue()
t1 = Thread(target=consumer, args=(q,))
t2 = Thread(target=producer, args=(q,))
t1.start()
t2.start()

Queue 对象已经包含了必要的锁,所以你可以通过它在多个线程间多安全地共享数据。 当使用队列时,协调生产者和消费者的关闭问题可能会有一些麻烦。一个通用的解决方法是在队列中放置一个特殊的值,当消费者读到这个值的时候,终止执行。例如:

from queue import Queue
from threading import Thread

# Object that signals shutdown
_sentinel = object()

# A thread that produces data
def producer(out_q):
  while running:
    # Produce some data
    ...
    out_q.put(data)

  # Put the sentinel on the queue to indicate completion
  out_q.put(_sentinel)

# A thread that consumes data
def consumer(in_q):
  while True:
    # Get some data
    data = in_q.get()

    # Check for termination
    if data is _sentinel:
      in_q.put(_sentinel)
      break

    # Process the data
    ...

本例中有一个特殊的地方:消费者在读到这个特殊值之后立即又把它放回到队列中,将之传递下去。这样,所有监听这个队列的消费者线程就可以全部关闭了。 尽管队列是最常见的线程间通信机制,但是仍然可以自己通过创建自己的数据结构并添加所需的锁和同步机制来实现线程间通信。最常见的方法是使用 Condition 变量来包装你的数据结构。下边这个例子演示了如何创建一个线程安全的优先级队列

import heapq
import threading

class PriorityQueue:
  def __init__(self):
    self._queue = []
    self._count = 0
    self._cv = threading.Condition()
  def put(self, item, priority):
    with self._cv:
      heapq.heappush(self._queue, (-priority, self._count, item))
      self._count += 1
      self._cv.notify()

  def get(self):
    with self._cv:
      while len(self._queue) == 0:
        self._cv.wait()
      return heapq.heappop(self._queue)[-1]

使用队列来进行线程间通信是一个单向、不确定的过程。通常情况下,你没有办法知道接收数据的线程是什么时候接收到的数据并开始工作的。不过队列对象提供一些基本完成的特性,比如下边这个例子中的 task_done() join()

from queue import Queue
from threading import Thread

# A thread that produces data
def producer(out_q):
  while running:
    # Produce some data
    ...
    out_q.put(data)

# A thread that consumes data
def consumer(in_q):
  while True:
    # Get some data
    data = in_q.get()

    # Process the data
    ...
    # Indicate completion
    in_q.task_done()

# Create the shared queue and launch both threads
q = Queue()
t1 = Thread(target=consumer, args=(q,))
t2 = Thread(target=producer, args=(q,))
t1.start()
t2.start()

# Wait for all produced items to be consumed
q.join()

如果一个线程需要在一个“消费者”线程处理完特定的数据项时立即得到通知,你可以把要发送的数据和一个 Event 放到一起使用,这样“生产者”就可以通过这个Event对象来监测处理的过程了。示例如下:

from queue import Queue
from threading import Thread, Event

# A thread that produces data
def producer(out_q):
  while running:
    # Produce some data
    ...
    # Make an (data, event) pair and hand it to the consumer
    evt = Event()
    out_q.put((data, evt))
    ...
    # Wait for the consumer to process the item
    evt.wait()

# A thread that consumes data
def consumer(in_q):
  while True:
    # Get some data
    data, evt = in_q.get()
    # Process the data
    ...
    # Indicate completion
    evt.set()

讨论

基于简单队列编写多线程程序在多数情况下是一个比较明智的选择。从线程安全队列的底层实现来看,你无需在你的代码中使用锁和其他底层的同步机制,这些只会把你的程序弄得乱七八糟。此外,使用队列这种基于消息的通信机制可以被扩展到更大的应用范畴,比如,你可以把你的程序放入多个进程甚至是分布式系统而无需改变底层的队列结构。 使用线程队列有一个要注意的问题是,向队列中添加数据项时并不会复制此数据项,线程间通信实际上是在线程间传递对象引用。如果你担心对象的共享状态,那你最好只传递不可修改的数据结构(如:整型、字符串或者元组)或者一个对象的深拷贝。例如:

from queue import Queue
from threading import Thread
import copy

# A thread that produces data
def producer(out_q):
  while True:
    # Produce some data
    ...
    out_q.put(copy.deepcopy(data))

# A thread that consumes data
def consumer(in_q):
  while True:
    # Get some data
    data = in_q.get()
    # Process the data
    ...

Queue 对象提供一些在当前上下文很有用的附加特性。比如在创建 Queue 对象时提供可选的 size 参数来限制可以添加到队列中的元素数量。对于“生产者”与“消费者”速度有差异的情况,为队列中的元素数量添加上限是有意义的。比如,一个“生产者”产生项目的速度比“消费者” “消费”的速度快,那么使用固定大小的队列就可以在队列已满的时候阻塞队列,以免未预期的连锁效应扩散整个程序造成死锁或者程序运行失常。在通信的线程之间进行“流量控制”是一个看起来容易实现起来困难的问题。如果你发现自己曾经试图通过摆弄队列大小来解决一个问题,这也许就标志着你的程序可能存在脆弱设计或者固有的可伸缩问题。 get() put() 方法都支持非阻塞方式和设定超时,例如:

import queue
q = queue.Queue()

try:
  data = q.get(block=False)
except queue.Empty:
  ...

try:
  q.put(item, block=False)
except queue.Full:
  ...

try:
  data = q.get(timeout=5.0)
except queue.Empty:
  ...

这些操作都可以用来避免当执行某些特定队列操作时发生无限阻塞的情况,比如,一个非阻塞的 put() 方法和一个固定大小的队列一起使用,这样当队列已满时就可以执行不同的代码。比如输出一条日志信息并丢弃。

def producer(q):
  ...
  try:
    q.put(item, block=False)
  except queue.Full:
    log.warning('queued item %r discarded!', item)

如果你试图让消费者线程在执行像 q.get() 这样的操作时,超时自动终止以便检查终止标志,你应该使用 q.get() 的可选参数 timeout ,如下:

_running = True

def consumer(q):
  while _running:
    try:
      item = q.get(timeout=5.0)
      # Process item
      ...
    except queue.Empty:
      pass

最后,有 q.qsize() q.full() q.empty() 等实用方法可以获取一个队列的当前大小和状态。但要注意,这些方法都不是线程安全的。可能你对一个队列使用 empty() 判断出这个队列为空,但同时另外一个线程可能已经向这个队列中插入一个数据项。所以,你最好不要在你的代码中使用这些方法。

以上就是Python如何实现线程间通信的详细内容,更多关于Python 线程间通信的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
Python字符串的encode与decode研究心得乱码问题解决方法
Mar 23 Python
python连接mongodb操作数据示例(mongodb数据库配置类)
Dec 31 Python
Django在Win7下的安装及创建项目hello word简明教程
Jul 14 Python
python实现ipsec开权限实例
Nov 11 Python
Python中的异常处理简明介绍
Apr 13 Python
python开启多个子进程并行运行的方法
Apr 18 Python
Python中使用Counter进行字典创建以及key数量统计的方法
Jul 06 Python
python虚拟环境的安装和配置(virtualenv,virtualenvwrapper)
Aug 09 Python
Django serializer优化类视图的实现示例
Jul 16 Python
详解python 支持向量机(SVM)算法
Sep 18 Python
Python可以用来做什么
Nov 23 Python
Pycharm安装python库的方法
Nov 24 Python
Python如何输出警告信息
Jul 30 #Python
Python设计密码强度校验程序
Jul 30 #Python
详解Pandas 处理缺失值指令大全
Jul 30 #Python
Python 爬虫的原理
Jul 30 #Python
Python爬虫与反爬虫大战
Jul 30 #Python
Python如何将装饰器定义为类
Jul 30 #Python
python实现mask矩阵示例(根据列表所给元素)
Jul 30 #Python
You might like
php 异常处理实现代码
2009/03/10 PHP
php array_filter除去数组中的空字符元素
2020/06/21 PHP
基于PHP的简单采集数据入库程序
2014/07/30 PHP
ThinkPHP通过AJAX返回JSON的两种实现方法
2014/12/18 PHP
浅谈COOKIE和SESSION区别
2015/07/19 PHP
PHP生成随机密码方法汇总
2015/08/27 PHP
php 使用expat方式解析xml文件操作示例
2019/11/26 PHP
ExtJs的Date格式字符代码
2010/12/30 Javascript
jquery实现表格奇数偶数行不同样式(有图为证及实现代码)
2013/01/23 Javascript
js相册效果代码(点击创建即可)
2013/04/16 Javascript
PHP PDO操作总结
2014/11/17 Javascript
js鼠标按键事件和键盘按键事件用法实例汇总
2016/10/03 Javascript
OkHttp踩坑随笔为何 response.body().string() 只能调用一次
2018/01/08 Javascript
浅谈JS对象添加getter与setter的5种方法
2018/06/09 Javascript
微信小程序支付前端源码
2018/08/29 Javascript
Vue.js递归组件实现组织架构树和选人功能
2019/07/04 Javascript
JavaScript定时器设置、使用与倒计时案例详解
2019/07/08 Javascript
微信小程序实现列表的横向滑动方式
2020/07/15 Javascript
[07:47]DOTA2国际邀请赛采访专栏:探访Valve总部
2013/08/08 DOTA
[01:39](回顾)各路豪强针锋相对,几经鏖战四强产生
2014/07/01 DOTA
[03:39]2015国际邀请赛主赛事首日精彩回顾
2015/08/05 DOTA
python用ConfigObj读写配置文件的实现代码
2013/03/04 Python
让 python 命令行也可以自动补全
2014/11/30 Python
python将秒数转化为时间格式的实例
2018/09/16 Python
python数据结构之线性表的顺序存储结构
2018/09/28 Python
python 批量解压压缩文件的实例代码
2019/06/27 Python
python实现多进程通信实例分析
2019/09/01 Python
python 字典有序并写入json文件过程解析
2019/09/30 Python
简单了解python数组的基本操作
2019/11/26 Python
Python如何使用正则表达式爬取京东商品信息
2020/06/01 Python
html5的pushstate以及监听浏览器返回事件的实现
2020/08/11 HTML / CSS
关于爱情的广播稿
2014/01/16 职场文书
群众路线教育实践活动实施方案
2014/10/31 职场文书
公司放假通知怎么写
2015/04/15 职场文书
优秀党员主要事迹范文
2015/11/05 职场文书
2019年“红色之旅”心得体会1000字(3篇)
2019/09/27 职场文书