python多任务,线程详解
python 多任务
多线程
python的thread模块是⽐较底层的模块,python的threading
模块是对thread做了⼀些包装的,可以更加⽅便的被使⽤
调用
1 直接调用
# –*– coding: utf-8 –*–
# @Time : 2019/1/7 22:21
# @Author : Damon_duanlei
# @FileName : thread_test01.py
# @BlogsAddr : https://blog.csdn.net/Damon_duanlei
import threading
import timedef hello_bye(name):print("hello! {}".format(name))time.sleep(1)print("bye! {}".format(name))if __name__ == '__main__':t1 = threading.Thread(target=hello_bye, args=("臭臭",))t2 = threading.Thread(target=hello_bye, args=("小迪",))t1.start()t2.start()
运行结果:
>>>
hello! 臭臭
hello! 小迪
bye! 臭臭
bye! 小迪
2 继承 Threading.Thread
# –*– coding: utf-8 –*–
# @Time : 2019/1/7 22:29
# @Author : Damon_duanlei
# @FileName : thread_test02.py
# @BlogsAddr : https://blog.csdn.net/Damon_duanleiimport threading
import timedef hello_bye(name):print("hello! {}".format(name))time.sleep(1)print("bye! {}".format(name))class MyThread(threading.Thread):def __init__(self, func, name):threading.Thread.__init__(self)self.func = funcself.name = namedef run(self):self.func(self.name)if __name__ == '__main__':t1 = MyThread(hello_bye, "臭臭")t2 = MyThread(hello_bye, "小迪")t1.start()t2.start()
运行结果:
>>>
hello! 臭臭
hello! 小迪
bye! 臭臭
bye! 小迪
区别
使用继承的方式重写run()方法,start()时不走父类的run()方法,而是走子类重写的run()方法.使用threading.Thread直接创建,start()的时候走Thread类下的run()方法,该run()方法会主动调用 target 函数. 所以使用继承方式调用多线程需要将业务逻辑写入重写后的run()方法.
线程的执行顺序
# –*– coding: utf-8 –*–
# @Time : 2019/1/13 11:07
# @Author : Damon_duanlei
# @FileName : thread_runing_order.py
# @BlogsAddr : https://blog.csdn.net/Damon_duanleiimport threading
import timeclass MyThread(threading.Thread):def run(self):for i in range(3):time.sleep(1)msg = "I'm " + self.name + ' @ ' + str(i)print(msg)def test():for i in range(5):t = MyThread()t.start()if __name__ == '__main__':test()
运行结果:( 运行的结果可能不一样, 但是大体一致)
>>>
I'm Thread-1 @ 0
I'm Thread-5 @ 0
I'm Thread-3 @ 0
I'm Thread-2 @ 0
I'm Thread-4 @ 0
I'm Thread-1 @ 1
I'm Thread-5 @ 1
I'm Thread-4 @ 1
I'm Thread-3 @ 1
I'm Thread-2 @ 1
I'm Thread-1 @ 2
I'm Thread-4 @ 2
I'm Thread-5 @ 2
I'm Thread-3 @ 2
I'm Thread-2 @ 2
结论:
多线程程序的执行顺序是不确定的. 当执行到 sleep 语句时, 线程将被阻塞( Blocked ), 到 sleep 结束后, 线程进入就绪状态( Runable ), 等待调度. 而线程调度间自行选择一个线程执行. 上面代码中能保证每个线程都运行完整个 run 函数, 但是线程的启动顺序及 run 函数中每次循环的执行顺序都不能确定.
阻塞
python 中主线程会等待所有的子线程都执行结束后才结束, 有时实际开发需要子线程开启后, 主线程等待子线程执行结束后再继续向下执行,可以使用 线程.join()
方法进行阻塞.(以下示例代码均使用直接调用线程的方法)
# –*– coding: utf-8 –*–
# @Time : 2019/1/7 22:21
# @Author : Damon_duanlei
# @FileName : thread_test01.py
# @BlogsAddr : https://blog.csdn.net/Damon_duanlei
import threading
import timedef hello_bye(name, thread_name):print("线程{}启动...".format(thread_name))print("hello! {}".format(name))time.sleep(3)print("bye! {}".format(name))time.sleep(3)print("线程{}结束...".format(thread_name))if __name__ == '__main__':t1 = threading.Thread(target=hello_bye, args=("臭臭", "t1"))t2 = threading.Thread(target=hello_bye, args=("小迪", "t2"))print("程序开始执行")t1.start()# t1.join()print("主线程继续向下执行")# t1.join()t2.start()t1.join()t2.join()print("主线程执行结束")
小伙伴可以尝试 t1.join()
三处位置不同执行的结果有什么差异.
结论:
线程名.join()添加在何处, 主线程就阻塞在何处等待该子线程执行结束后方解阻塞. 根据业务需求可以将线程的开启和添加阻塞写为类似以下代码结构:
thread_list = []t1 = threading.Thread(target=hello_bye, args=("臭臭", "t1"))thread_list.append(t1)t2 = threading.Thread(target=hello_bye, args=("小迪", "t2"))thread_list.append(t2)t3 = threading.Thread(target=hello_bye, args=("笨笨", "t3"))thread_list.append(t3)t4 = threading.Thread(target=hello_bye, args=("白克", "t4"))thread_list.append(t4)for t in thread_list:t.start()for t in thread_list:t.join()
多线程共享全局变量
# –*– coding: utf-8 –*–
# @Time : 2019/1/13 11:28
# @Author : Damon_duanlei
# @FileName : thread_globle_var.py
# @BlogsAddr : https://blog.csdn.net/Damon_duanlei
from threading import Thread
import timeg_num = 100def work1():global g_numfor i in range(3):g_num += 1print("----in work1, g_num is %d---" % g_num)def work2():global g_numprint("----in work2, g_num is %d---" % g_num)if __name__ == '__main__':print("---线程创建之前g_num is %d---" % g_num)t1 = Thread(target=work1)t1.start()# 延时,保证t1线程中的事情做完time.sleep(1)t2 = Thread(target=work2)t2.start()
运行结果:
>>>
---线程创建之前g_num is 100---
----in work1, g_num is 103---
----in work2, g_num is 103---
结论:在一个进程内所有线程共享全局变量, 很方便多个线程间共享数据, 缺点就是,线程对全局变量随意修改可能造成多线程之间全局变量的混乱( 即线程非安全)
多线程共享全局变量问题
多线程开发可能遇到的问题
假设两个线程 t1 和 t2 都要对全局变量进行加1运算(g_num = 0), t1 和 t2 都各自对同一个全局变量加10次, g_num 的结果应该为20. 但是由于多线程同时操作, 很有可能出现下面的情况:
在g_num = 0 时, t1 取得 g_num = 0. 此时系统把 t1 调度为 sleeping 状态, 把t2转换为 “running” 状态, t2 也获得 g_num = 0. 然后 t2 对得到的值进行加 1 并赋值给 g_num, 使得 g_num = 1. 然后系统又把 t2 调度为 sleeping 状态, 把 t1 转换为 running . 线程 t1 有吧他之前得到的0加1后赋值给 g_num. 这样导致虽然 t1和 t2 都对 g_num 加1 , 但结果仍然是 g_num = 1
测试
# –*– coding: utf-8 –*–
# @Time : 2019/1/13 14:13
# @Author : Damon_duanlei
# @FileName : test_01.py
# @BlogsAddr : https://blog.csdn.net/Damon_duanlei
import threading
g_num = 0def work1(num):global g_numfor i in range(num):g_num += 1print("----in work1, g_num is %d---" % g_num)def work2(num):global g_numfor i in range(num):g_num += 1print("----in work2, g_num is {}---".format(g_num))print("---线程创建之前g_num is {}---".format(g_num))
t1 = threading.Thread(target=work1, args=(1000000,))
t1.start()
t2 = threading.Thread(target=work2, args=(1000000,))
t2.start()
t1.join()
t2.join()
print("2个线程对同⼀个全局变量操作之后的最终结果是:{}".format(g_num))
运行结果:
---线程创建之前g_num is 0---
----in work1, g_num is 1333706---
----in work2, g_num is 1450558---
2个线程对同⼀个全局变量操作之后的最终结果是:1450558
结论:
- 如果多个线程同时对一个全局变量操作, 会出现资源竞争问题, 从而数据结果会不正确.
同步的概念
同步就是协同步调, 按预定的先后次序进行运行. "同"字从字面上容易理解为一起动作,其实不是, "同"字应是指协同, 协助, 相互配合. 如: 进程,线程同步, 可理解为进程或线程A 和 B 一块配合, A执行到一定程度时要依靠 B 的某个结果, 于是停下来, 示意B 运行; B执行,得到结果后, 再将结果给A; A 再继续操作.
解决线程同时修改全局变量的方式
思路如下:
- 系统调用 t1, 然后获取到 g_num 的值为0, 此时上一把锁, 即不允许其他线程操作g_num
- t1 对 g_num 的值进行 + 1
- t1 解锁, 此时g_num的值为1, 他她的县城就可以使用 g_num了, 且g_num 的值不是 0 而是1.
- 同理其他线程对 g_num 进行修改时, 都要先上锁, 处理完后再解锁, 在上锁的整个过程中不允许其他线程访问, 这就保证了数据的正确性.
互斥锁
当多个线程几乎同时修改某个共享数据的时候, 需要进行同步控制. 线程同步能够保证多个线程安全访问竞争资源, 最简单的同步机制是引入互斥锁
互斥锁为资源引入一个状态: 锁定/非锁定
某个线程要更改共享数据时, 先将其锁定, 此时资源的状态为"锁定",其他线程不能更改; 直到该线程释放资源, 将资源的状态变成 “非锁定”, 其他的线程才能再次锁定该资源. 互斥锁保证了每次只有一个线程进行写入操作, 从而保证了多线程情况下数据的准确性.
threading 模块中定义了 Lock 类, 可以方便的处理锁定:
# 创建锁
lock = threading.Lock()
# 锁定
lock.acquire()
# 释放锁
lock.release()
注意:
- 如果这个锁之前没有上锁, 那么acquire 不会阻塞
- 如果在调用 acquire 上锁之前它已经被其他线程上了锁, 那么此时 qcquire 会阻塞, 直到这个锁被解锁为止
对上文测试代码引入互斥锁后代码及运行结果:
# –*– coding: utf-8 –*–
# @Time : 2019/1/13 14:47
# @Author : Damon_duanlei
# @FileName : thread_lock.py
# @BlogsAddr : https://blog.csdn.net/Damon_duanlei
import threading
g_num = 0def test1(num):global g_numfor i in range(num):mutex.acquire() # 上锁g_num += 1mutex.release() # 解锁print("---test1---g_num={}".format(g_num))def test2(num):global g_numfor i in range(num):mutex.acquire() # 上锁g_num += 1mutex.release() # 解锁print("---test2---g_num={}".format(g_num))# 创建⼀个互斥锁
# 默认是未上锁的状态
mutex = threading.Lock()
# 创建2个线程,让他们各⾃对g_num加1000000次
t1 = threading.Thread(target=test1, args=(1000000,))
t1.start()
t2 = threading.Thread(target=test2, args=(1000000,))
t2.start()
# 等待计算完成
t1.join()
t2.join()
print("2个线程对同⼀个全局变量操作之后的最终结果是:{}".format(g_num))
运行结果:
>>>
---test2---g_num=1997389
---test1---g_num=2000000
2个线程对同⼀个全局变量操作之后的最终结果是:2000000
可以看到最后的结果, 加入互斥锁后, 其结果与预期相符.
锁的好处:
- 确保了某段关键代码只能由一个线程从头到尾完整的执行
锁的坏处:
- 阻止了多线程并发执行, 包含锁的某段代码实际上只能以单线程模式执行,效率大大下降.
- 由于可以存在多个锁, 不同线程持有不同的锁, 并试图获取对方持有的锁时,可能会造成死锁.
避免死锁:
- 程序设计时要劲量避免 ( 银行家算法 )
- 添加超时时间
GIL全局解释器锁
GIL是什么?为什么会有GIL? 网上有茫茫多的答案, 感兴趣的小伙伴请自行了解, 总之,因为 GIL 的存在Cpython的解释器同时只有一个线程运行. 因此python的多线程在面对计算密集型( CPU密集型 ) 相比单线程没有效率优势,甚至在python2.7之前面对计算密集型多线程效率远远低于单线程效率. 但是Cpython的多线程并非鸡肋, 在IO密集型程序中,因多次进行IO操作反复对线程进行阻塞,在等待阻塞的时间系统可以将线程调用在非阻塞的地方.所以在IO密集型程序中python多线程效率远远大于单线程效率.
线程池
待续 …
python多任务,线程详解相关推荐
- Python线程详解
Python线程详解 线程简介 开启多线程 线程之间共享 GIL全局解释器锁 线程间通信 线程简介 线程,有时被称为轻量进程(Lightweight Process,LWP),是程序执行流的最小单元. ...
- python与golang_Golang与python线程详解及简单实例
Golang与python线程详解及简单实例 在GO中,开启15个线程,每个线程把全局变量遍历增加100000次,因此预测结果是 15*100000=1500000. var sum int var ...
- python time模块详解
python time模块详解 转自:http://blog.csdn.net/kiki113/article/details/4033017 python 的内嵌time模板翻译及说明 一.简 ...
- Python开发技术详解PDF
Python开发技术详解(高清版)PDF 百度网盘 链接:https://pan.baidu.com/s/1F5J9mFfHKgwhkC5KuPd0Pw 提取码:xxy3 复制这段内容后打开百度网盘手 ...
- python协程详解
目录 python协程详解 一.什么是协程 二.了解协程的过程 1.yield工作原理 2.预激协程的装饰器 3.终止协程和异常处理 4.让协程返回值 5.yield from的使用 6.yield ...
- python嗅探工具详解附源码(使用socket,带tkinter界面)
python嗅探工具详解(带tkinter界面) 点击详见原理 点击详见原理 TCP/IP协议号补充 必备基础知识 IP数据包格式 详见点此 TCP报文格式 详见点此 struct模块 在Python ...
- Python开发技术详解
Python开发技术详解 下载地址 https://pan.baidu.com/s/1KTrk3B1sajMiPIoo3-Rhuw 扫码下面二维码关注公众号回复 100089获取分享码 本书目录结构如 ...
- python开发技术详解pdf下载_python开发技术详解附源码-python开发技术详解电子书pdf下载高清去水印版-精品下载...
Python开发技术详解适合Python爱好者.大中专院校的学生.社会培训班的学生以及用Python语言进行系统管理.GUI开发.Web开发.数据库编程.网络编程的人员使用. 内容提要 Python是 ...
- python协程详解_python协程详解
原博文 2019-10-25 10:07 − # python协程详解 ![python协程详解](https://pic2.zhimg.com/50/v2-9f3e2152b616e89fbad86 ...
最新文章
- 近期必读的9篇CVPR 2019【域自适应(Domain Adaptation)】相关论文和代码
- appium 如何调用 adb_带你了解可用于Android APP自动化测试的框架:Appium
- 谈一谈对旋转矩阵的理解
- php 设为首页 收藏_如何在网站上添加“设为首页”“加入收藏”
- 2019蓝桥杯B组:完全二叉树权值
- 推荐3个好用的Excel项目管理甘特图模板
- java 获取本机地址_java如何获取本机IP地址
- 2018校招笔试真题汇总
- mysql快照数据_制作mysql数据快照
- 学期博客:学习进度条
- 3. 布莱叶盲文与二进制码
- c语言eval函数,百行代码轻便实现C#中的Eval函数
- 试用期、见习期、实习期、合同期、服务期的区别与应用
- c语言将一个字符串转置,c语言实现数组的转置
- 万圣节日丧尸变异头像生成流量主小程序开发
- Geek的卸载存在小小缺憾
- 银河麒麟系统中的串口调试
- ZLMediaKit源码分析 - NotifyCenter
- python数据分析处理库-Pandas
- 洛谷入门1【顺序结构】题单题解
热门文章
- 紫光展锐发布系统级安全的高性能5G SoC移动平台T820
- 雍正杀“舅”:握着领导把柄,隆科多必须死?
- macos 安装cms php,苹果cms安装及配置详细教程
- java isreachable_Java网络编程从入门到精通(12):使用isReachable方法探测主机是否可以连通...
- 计算机 无法进入睡眠模式,win10电脑无法进入睡眠模式怎么解决
- Docker基础(三)—配置镜像加速器
- linux 部署 程序,Linux 部署配置WEB APP
- 吴乙己的数仓指南_2维度建模核心思想
- 数数(数学题运算分配律)
- Vue+Vux实现登录