python 实现线程之间的通信示例


Posted in Python onFebruary 14, 2020

前言:因为GIL的限制,python的线程是无法真正意义上并行的。相对于异步编程,其性能可以说不是一个等量级的。为什么我们还要学习多线程编程呢,虽然说异步编程好处多,但编程也较为复杂,逻辑不容易理解,学习成本和维护成本都比较高。毕竟我们大部分人还是适应同步编码的,除非一些需要高性能处理的地方采用异步。

首先普及下进程和线程的概念:

进程:进程是操作系统资源分配的基本单位。

线程:线程是任务调度和执行的基本单位。

一个应用程序至少一个进程,一个进程至少一个线程。

两者区别:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。

一、多线程

python 可以通过 thread 或 threading 模块实现多线程,threading 相比 thread 提供了更高阶、更全面的线程管理。我们下文主要以 threading 模块介绍多线程的基本用法。

import threading
import time

class thread(threading.Thread):
 def __init__(self, threadname):
 threading.Thread.__init__(self, name='线程' + threadname)

 def run(self):
 print('%s:Now timestamp is %s'%(self.name,time.time()))

threads = []
for a in range(int(5)): # 线程个数
 threads.append(thread(str(a)))
for t in threads: # 开启线程
 t.start()
for t in threads: # 阻塞线程
 t.join()
print('END')

输出:
线程3:Now timestamp is 1557386184.7574518
线程2:Now timestamp is 1557386184.7574518
线程0:Now timestamp is 1557386184.7574518
线程1:Now timestamp is 1557386184.7574518
线程4:Now timestamp is 1557386184.7582724
END

 start() 方法开启子线程。运行多次 start() 方法代表开启多个子线程。

 join() 方法用来阻塞主线程,等待子线程执行完成。举个例子,主线程A创建了子线程B,并使用了 join() 方法,主线程A在 join() 处就被阻塞了,等待子线程B完成后,主线程A才能执行 print('END')。如果没有使用 join() 方法,主线程A创建子线程B后,不会等待子线程B,直接执行 print('END'),如下:

import threading
import time

class thread(threading.Thread):
 def __init__(self, threadname):
 threading.Thread.__init__(self, name='线程' + threadname)

 def run(self):
 time.sleep(1)
 print('%s:Now timestamp is %s'%(self.name,time.time()))

threads = []
for a in range(int(5)): # 线程个数
 threads.append(thread(str(a)))
for t in threads: # 开启线程
 t.start()
# for t in threads: # 阻塞线程
# t.join()
print('END')

输出:
END
线程0:Now timestamp is 1557386321.376941
线程3:Now timestamp is 1557386321.377937
线程1:Now timestamp is 1557386321.377937
线程2:Now timestamp is 1557386321.377937
线程4:Now timestamp is 1557386321.377937

二、线程之间的通信

1.threading.Lock()

如果多个线程对某一资源同时进行修改,可能会存在不可预知的情况。为了修改数据的正确性,需要把这个资源锁住,只允许线程依次排队进去获取这个资源。当线程A操作完后,释放锁,线程B才能进入。如下脚本是开启多个线程修改变量的值,但输出结果每次都不一样。

import threading

money = 0
def Order(n):
 global money
 money = money + n
 money = money - n

class thread(threading.Thread):
 def __init__(self, threadname):
 threading.Thread.__init__(self, name='线程' + threadname)
 self.threadname = int(threadname)

 def run(self):
 for i in range(1000000):
  Order(self.threadname)

t1 = thread('1')
t2 = thread('5')
t1.start()
t2.start()
t1.join()
t2.join()
print(money)

接下来我们用 threading.Lock() 锁住这个变量,等操作完再释放这个锁。lock.acquire() 给资源加一把锁,对资源处理完成之后,lock.release() 再释放锁。以下脚本执行结果都是一样的,但速度会变慢,因为线程只能一个个的通过。

import threading

money = 0
def Order(n):
 global money
 money = money + n
 money = money - n

class thread(threading.Thread):
 def __init__(self, threadname):
 threading.Thread.__init__(self, name='线程' + threadname)
 self.threadname = int(threadname)

 def run(self):
 for i in range(1000000):
  lock.acquire()
  Order(self.threadname)
  lock.release()
# print('%s:Now timestamp is %s'%(self.name,time.time()))

lock = threading.Lock()
t1 = thread('1')
t2 = thread('5')
t1.start()
t2.start()
t1.join()
t2.join()
print(money)

2.threading.Rlock()

用法和 threading Lock() 一致,区别是 threading.Rlock() 允许多次锁资源,acquire() 和 release() 必须成对出现,也就是说加了几把锁就得释放几把锁。

lock = threading.Lock()
# 死锁
lock.acquire()
lock.acquire()
print('...')
lock.release()
lock.release()

rlock = threading.RLock()
# 同一线程内不会阻塞线程
rlock.acquire()
rlock.acquire()
print('...')
rlock.release()
rlock.release()

3.threading.Condition()

threading.Condition() 可以理解为更加高级的锁,比 Lock 和 Rlock 的用法更高级,能处理一些复杂的线程同步问题。threading.Condition() 创建一把资源锁(默认是Rlock),提供 acquire() 和 release() 方法,用法和 Rlock 一致。此外 Condition 还提供 wait()、Notify() 和 NotifyAll() 方法。

wait():线程挂起,直到收到一个 Notify() 通知或者超时(可选参数),wait() 必须在线程得到 Rlock 后才能使用。

Notify() :在线程挂起的时候,发送一个通知,让 wait() 等待线程继续运行,Notify() 也必须在线程得到 Rlock 后才能使用。 Notify(n=1),最多唤醒 n 个线程。

NotifyAll() :在线程挂起的时候,发送通知,让所有 wait() 阻塞的线程都继续运行。

举例说明下 Condition() 使用

import threading,time

def TestA():
 cond.acquire()
 print('李白:看见一个敌人,请求支援')
 cond.wait()
 print('李白:好的')
 cond.notify()
 cond.release()

def TestB():
 time.sleep(2)
 cond.acquire()
 print('亚瑟:等我...')
 cond.notify()
 cond.wait()
 print('亚瑟:我到了,发起冲锋...')

if __name__=='__main__':
 cond = threading.Condition()
 testA = threading.Thread(target=TestA)
 testB = threading.Thread(target=TestB)
 testA.start()
 testB.start()
 testA.join()
 testB.join()

输出
李白:看见一个敌人,请求支援
亚瑟:等我...
李白:好的
亚瑟:我到了,发起冲锋...

4.threading.Event()

threading.Event() 原理是在线程中立了一个 Flag ,默认值是 False ,当一个或多个线程遇到 event.wait() 方法时阻塞,直到 Flag 值 变为 True 。threading.Event() 通常用来实现线程之间的通信,使一个线程等待其他线程的通知 ,把 Event 传递到线程对象中。

event.wait() :阻塞线程,直到 Flag 值变为 True

event.set() :设置 Flag 值为 True

event.clear() :修改 Flag 值为 False

event.isSet() :  仅当 Flag 值为 True 时返回

下面这个例子,主线程启动子线程后 sleap 2秒,子线程因为 event.wait() 被阻塞。当主线程醒来后执行 event.set() ,子线程才继续运行,两者输出时间差 2s。

import threading
import datetime,time

class thread(threading.Thread):
 def __init__(self, threadname):
 threading.Thread.__init__(self, name='线程' + threadname)
 self.threadname = int(threadname)

 def run(self):
 event.wait()
 print('子线程运行时间:%s'%datetime.datetime.now())

if __name__ == '__main__':
 event = threading.Event()
 t1 = thread('0')
 #启动子线程
 t1.start()
 print('主线程运行时间:%s'%datetime.datetime.now())
 time.sleep(2)
 # Flag设置成True
 event.set()
 t1.join()

输出
主线程运行时间:2019-05-30 15:51:49.690872
子线程运行时间:2019-05-30 15:51:51.691523

 5.其他方法

threading.active_count():返回当前存活的线程对象的数量

threading.current_thread():返回当前线程对象

threading.enumerate():返回当前所有线程对象的列表

threading.get_ident():返回线程pid

threading.main_thread():返回主线程对象

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

Python 相关文章推荐
Python列表(list)常用操作方法小结
Feb 02 Python
剖析Python的Tornado框架中session支持的实现代码
Aug 21 Python
简单谈谈Python流程控制语句
Dec 04 Python
5个很好的Python面试题问题答案及分析
Jan 19 Python
python 筛选数据集中列中value长度大于20的数据集方法
Jun 14 Python
解决pip install xxx报错SyntaxError: invalid syntax的问题
Nov 30 Python
python 抓包保存为pcap文件并解析的实例
Jul 23 Python
python3操作注册表的方法(Url protocol)
Feb 05 Python
win10从零安装配置pytorch全过程图文详解
May 08 Python
基于Python的Jenkins的二次开发操作
May 12 Python
Django model重写save方法及update踩坑详解
Jul 27 Python
python 制作磁力搜索工具
Mar 04 Python
Python3变量与基本数据类型用法实例分析
Feb 14 #Python
OpenCV中VideoCapture类的使用详解
Feb 14 #Python
Python networkx包的实现
Feb 14 #Python
python常用运维脚本实例小结
Feb 14 #Python
Python如何在DataFrame增加数值
Feb 14 #Python
python turtle工具绘制四叶草的实例分享
Feb 14 #Python
Python阶乘求和的代码详解
Feb 14 #Python
You might like
PHP一些有意思的小区别
2006/12/06 PHP
PHP XML error parsing SOAP payload on line 1
2010/06/17 PHP
php设计模式 Mediator (中介者模式)
2011/06/26 PHP
使用 PHPMAILER 发送邮件实例应用
2012/11/07 PHP
php图片添加文字水印实现代码
2016/03/15 PHP
php 实现进制相互转换
2016/04/07 PHP
Yii使用DeleteAll连表删除出现报错问题的解决方法
2016/07/14 PHP
Thinkphp框架开发移动端接口(2)
2016/08/18 PHP
Javascript remove 自定义数组删除方法
2009/10/20 Javascript
发布一个基于javascript的动画类 Fx.js
2010/11/05 Javascript
extjs中grid中嵌入动态combobox的应用
2011/01/01 Javascript
各情景下元素宽高的获取实现代码
2011/09/13 Javascript
js判断为空Null与字符串为空简写方法
2014/02/24 Javascript
轻松理解JavaScript闭包
2017/03/14 Javascript
javascript数据结构之串的概念与用法分析
2017/04/12 Javascript
使用vuex的state状态对象的5种方式
2018/04/19 Javascript
完美解决通过IP地址访问VUE项目的问题
2020/07/18 Javascript
详解Vue.js 可拖放文本框组件的使用
2021/03/03 Vue.js
[02:08]2014DOTA2国际邀请赛 430专访:力争取得小组前二
2014/07/11 DOTA
使用Python的Tornado框架实现一个简单的WebQQ机器人
2015/04/24 Python
Python多进程并发(multiprocessing)用法实例详解
2015/06/02 Python
win与linux系统中python requests 安装
2016/12/04 Python
python实时监控cpu小工具
2018/06/21 Python
python3读取excel文件只提取某些行某些列的值方法
2018/07/10 Python
Django 日志配置按日期滚动的方法
2019/01/31 Python
ubuntu 18.04 安装opencv3.4.5的教程(图解)
2019/11/04 Python
Python imread、newaxis用法详解
2019/11/04 Python
Python3爬虫关于代理池的维护详解
2020/07/30 Python
PyTorch安装与基本使用详解
2020/08/31 Python
利用CSS3制作简单的3d半透明立方体图片展示
2017/03/25 HTML / CSS
小学教师培训感言
2014/02/11 职场文书
《金色的脚印》教后反思
2014/04/23 职场文书
2014年银行信贷员工作总结
2014/12/08 职场文书
pytorch 运行一段时间后出现GPU OOM的问题
2021/06/02 Python
SQL Server中常用截取字符串函数介绍
2022/03/16 SQL Server
MySQL数据库配置信息查看与修改方法详解
2022/06/25 MySQL