Python中的线程间通信

文章目录

  • Python中的线程间通信
    • 1.Queue
    • 2.同步机制
      • 1.Event
      • 2.Semaphore(信号量)
      • 3.Lock(锁)
      • 4.RLock(可重入锁)
      • 5.Condition(条件变量)

1.Queue

使用线程队列有一个要注意的问题是,向队列中添加数据项时并不会复制此数据项,线程间通信实际上是在线程间传递对象引用。如果你担心对象的共享状态,那你最好只传递不可修改的数据结构(如:整型、字符串或者元组)或者一个对象的深拷贝

  • 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:
...
def producer(q):...try:q.put(item, block=False)except queue.Full:log.warning('queued item %r discarded!', item)
_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() 判断出这个队列为空,但同时另外一个线程可能已经向这个队列中插入一个数据项。所以,你最好不要在你的代码中使用这些方法。

为了避免出现死锁的情况,使用锁机制的程序应该设定为每个线程一次只允许获取一个锁。如果不能这样做的话,你就需要更高级的死锁避免机制。在 threading 库中还提供了其他的同步原语,比如 RLock 和 Semaphore 对象。

Queue提供的方法

task_done()

意味着之前入队的一个任务已经完成。由队列的消费者线程调用。每一个get()调用得到一个任务,接下来的task_done()调用告诉队列该任务已经处理完毕

如果当前一个join()正在阻塞,它将在队列中的所有任务都处理完时恢复执行(即每一个由put()调用入队的任务都有一个对应的task_done()调用)

join()

阻塞调用线程,直到队列中的所有任务被处理掉。

只要有数据被加入队列,未完成的任务数就会增加。当消费者线程调用task_done()(意味着有消费者取得任务并完成任务),未完成的任务数就会减少。当未完成的任务数降到0,join()解除阻塞。

put(item[, block[, timeout]])
将item放入队列中。

1.如果可选的参数block为True且timeout为空对象(默认的情况,阻塞调用,无超时)。
2.如果timeout是个正整数,阻塞调用进程最多timeout秒,如果一直无空空间可用,抛出Full异常(带超时的阻塞调用)。
3.如果block为False,如果有空闲空间可用将数据放入队列,否则立即抛出Full异常

其非阻塞版本为put_nowait等同于put(item, False)

get([block[, timeout]])
从队列中移除并返回一个数据。block跟timeout参数同put方法

其非阻塞方法为get_nowait()相当与get(False)

empty()
如果队列为空,返回True,反之返回False

2.同步机制

1.Event

  • 线程的一个关键特性是每个线程都是独立运行且状态不可预测
  • 如果程序中的其他线程需要通过断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。
  • 为了解决这些问题,我们需要使用 threading 库中的 Event 对象。Event 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生
  • 在初始情况下,event 对象中的信号标志被设置假。如果有线程等待一个 event 对象,而这个 event 对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。
  • 一个线程如果将一个 event 对象的信号标志设置为真,它将唤醒所有等待个 event 对象的线程。如果一个线程等待一个已经被设置为真的 event 对象,那么它将忽略这个事件,继续执行。
from threading import Thread, Event
import timedef countdown(n, start_evt):print('countdown is starting...')start_evt.set()while n > 0:print('T-minus', n)n -= 1time.sleep(5)start_evt = Event()  # 可通过Event 判断线程的是否已运行
t = Thread(target=countdown, args=(10, start_evt))
t.start()print('launching countdown...')
start_evt.wait()  # 等待countdown执行# event 对象的一个重要特点是当它被设置为真时会唤醒所有等待它的线程print('countdown is running...')

2.Semaphore(信号量)

在多线程编程中,为了防止不同的线程同时对一个公用的资源(比如全部变量)进行修改,需要进行同时访问的数量(通常是1)的限制。信号量同步基于内部计数器,每调用一次acquire(),计数器减1;每调用一次release(),计数器加1.当计数器为0时,acquire()调用被阻塞

from threading import Semaphore, Lock, RLock, Condition, Event, Thread
import time
# 信号量
sema = Semaphore(3)  #限制同时能访问资源的数量为3def foo(tid):with sema:print('{} acquire sema'.format(tid))time.sleep(1)print('{} release sema'.format(tid))threads = []for i in range(5):t = Thread(target=foo, args=(i,))threads.append(t)t.start()for t in threads:t.join()

3.Lock(锁)

互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性

#创建锁
mutex = threading.Lock()
#锁定
mutex.acquire([timeout])
#释放
mutex.release()

4.RLock(可重入锁)

为了支持在同一线程中多次请求同一资源,python提供了“可重入锁”:threading.RLock。RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源

import threading
import timeclass MyThread(threading.Thread):def run(self):global num time.sleep(1)if mutex.acquire(1):  num = num+1msg = self.name+' set num to '+str(num)print msgmutex.acquire()mutex.release()mutex.release()
num = 0
mutex = threading.RLock()
def test():for i in range(5):t = MyThread()t.start()
if __name__ == '__main__':test()

5.Condition(条件变量)

  • Condition被称为条件变量,除了提供与Lock类似的acquire和release方法外,还提供了wait和notify方法。线程首先acquire一个条件变量,然后判断一些条件。如果条件不满足则wait;如果条件满足,进行一些处理改变条件后,通过notify方法通知其他线程,其他处于wait状态的线程接到通知后会重新判断条件。不断的重复这一过程,从而解决复杂的同步问题
  • 可以认为Condition对象维护了一个锁(Lock/RLock)和一个waiting池。线程通过acquire获得Condition对象,当调用wait方法时,线程会释放Condition内部的锁并进入blocked状态,同时在waiting池中记录这个线程。当调用notify方法时,Condition对象会从waiting池中挑选一个线程,通知其调用acquire方法尝试取到锁
  • Condition对象的构造函数可以接受一个Lock/RLock对象作为参数,如果没有指定,则Condition对象会在内部自行创建一个RLock。
  • 除了notify方法外,Condition对象还提供了notifyAll方法,可以通知waiting池中的所有线程尝试acquire内部锁。由于上述机制,处于waiting状态的线程只能通过notify方法唤醒,所以notifyAll的作用在于防止有线程永远处于沉默状态
import threading
import timeclass Producer:def run(self):global countwhile True:if con.acquire():if count > 1000:con.wait()else:count += 100msg = threading.current_thread().name + ' produce 100, count=' + str(count)print(msg)con.notify()  # 通知 waiting线程池中的线程con.release()time.sleep(1)count = 0
con = threading.Condition()class Consumer:def run(self):global countwhile True:if con.acquire():if count < 100:con.wait()else:count -= 3msg = threading.current_thread().name + ' consumer 3, count=' + str(count)print(msg)con.notify()con.release()time.sleep(3)producer = Producer()
consumer = Consumer()

Python中的线程间通信相关推荐

  1. 线程间通信的几种方法_并发编程中的线程间通信

    线程通信的目标是使线程间能够互相发送信号.另一方面,线程通信使线程能够等待其他线程的信号. 线程通信常用的方式有: wait/notify 等待 Volatile 内存共享 CountDownLatc ...

  2. python 多线程及线程间通信

    python 多线程 import threading import time def run(n):print("task", n)time.sleep(1)print('2s' ...

  3. python 线程通信 会涉及到拷贝吗_Python如何实现线程间通信

    问题 你的程序中有多个线程,你需要在这些线程之间安全地交换信息或数据 解决方案 从一个线程向另一个线程发送数据最安全的方式可能就是使用 queue 库中的队列了.创建一个被多个线程共享的 Queue ...

  4. Java并发——线程间通信与同步技术

    传统的线程间通信与同步技术为Object上的wait().notify().notifyAll()等方法,Java在显示锁上增加了Condition对象,该对象也可以实现线程间通信与同步.本文会介绍有 ...

  5. Python中线程间通信

    Python中线程间通信 一.前言 二.什么是互斥锁 三.使用互斥锁 四.使用队列在线程间通信 五.关于线程需要注意的两点 一.前言   我们已经知道进程之间不能直接共享信息,那么线程之间可以共享信息 ...

  6. python 线程通信的几种方式_进程间通信和线程间通信的几种方式

    进程 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础.在早期面向进程设计的计算机结构中,进程是程序的基本执行实体:在当代 ...

  7. python协程和线程区别_python中的线程和协程之间有什么区别

    一.首先我们来了解一下线程和协程的概念1.线程线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源( ...

  8. python线程间通信_python多线程之事件触发(线程间通信)

    执行结果: 那么,通过分析执行结果来看,您已经体会到了其中的秘密.... 再脑补一下: Python提供了Event对象用于线程间通信,它是由线程设置的信号标志,如果信号标志位真,则其他线程等待直到信 ...

  9. python线程间通信方法之Event

    最近写程序要用到线程间通信,在网上搜了些资料,测试了一下代码,在这里总结一下. Python实现线程间通信有几种方法,在这里介绍Event对象. Event对象实现了简单的线程通信机制,它提供了设置信 ...

最新文章

  1. RDKit | 基于随机森林(RF)预测SARS-CoV 3CL蛋白酶抑制剂的pIC50
  2. 职教云python题和答案_智慧职教云课堂Python程序设计题目答案
  3. shell之实战应用一(查找xml文档中的关键字段)
  4. java枚举.toString_java – Make Enum.toString()本地化
  5. SAP CRM One Order object type in line item - when it is filled
  6. python职场应用_大学粗略学习过Python,在进入职场后如何进一步学习Python
  7. C# 读写ini配置文件demo
  8. python判断正负零_【译】格式字符串语法
  9. 《iOS面试之道》算法基础学习(上)
  10. WFDB软件包简介——ECG数据在Matlab下的读取/显示方法
  11. qt可以实现创建临时无线热点吗?_数据线断了,身边又没有路由,如何无线高速传输文件?...
  12. Ubuntu 18.04安装: failed to load ldlinux.c32
  13. Opencv实现身份证OCR识别
  14. 三大控制结构 js函数定义
  15. CRM系统有哪些效果?
  16. 怎么使用Navicat连接数据库?
  17. 2023年国家留学基金委(CSC)有关国别申请、派出注意事项
  18. YOLOv5 Head解耦
  19. python miio 连接小米网关_小米门窗传感器2本质是感应门窗开合的,结果更好用的却是光线传感器...
  20. AWVS安装(Windows)

热门文章

  1. sketchup作品_18级园林工程技术专业课程实训作品展
  2. python两个数相加时_怎么用python让两个小数相加
  3. SpringBoot项目使用微服务后在Service窗口启动应用后不显示端口号
  4. DESIGN_OUTLINE' and 'CUTOUT' are the preferred subclasWARNING
  5. DevOps自动化工具集合
  6. NIO - Selector源码分析
  7. 《配置管理最佳实践》——1.2 从哪里开始
  8. [笔记][mooc]《程序设计入门—C语言》
  9. 教你如何将UIImageView视图中的图片变成圆角
  10. CodeForces - 1538G Gift Set(二分)