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 再继续操作.

解决线程同时修改全局变量的方式

思路如下:

  1. 系统调用 t1, 然后获取到 g_num 的值为0, 此时上一把锁, 即不允许其他线程操作g_num
  2. t1 对 g_num 的值进行 + 1
  3. t1 解锁, 此时g_num的值为1, 他她的县城就可以使用 g_num了, 且g_num 的值不是 0 而是1.
  4. 同理其他线程对 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多任务,线程详解相关推荐

  1. Python线程详解

    Python线程详解 线程简介 开启多线程 线程之间共享 GIL全局解释器锁 线程间通信 线程简介 线程,有时被称为轻量进程(Lightweight Process,LWP),是程序执行流的最小单元. ...

  2. python与golang_Golang与python线程详解及简单实例

    Golang与python线程详解及简单实例 在GO中,开启15个线程,每个线程把全局变量遍历增加100000次,因此预测结果是 15*100000=1500000. var sum int var ...

  3. python time模块详解

    python time模块详解 转自:http://blog.csdn.net/kiki113/article/details/4033017 python 的内嵌time模板翻译及说明    一.简 ...

  4. Python开发技术详解PDF

    Python开发技术详解(高清版)PDF 百度网盘 链接:https://pan.baidu.com/s/1F5J9mFfHKgwhkC5KuPd0Pw 提取码:xxy3 复制这段内容后打开百度网盘手 ...

  5. python协程详解

    目录 python协程详解 一.什么是协程 二.了解协程的过程 1.yield工作原理 2.预激协程的装饰器 3.终止协程和异常处理 4.让协程返回值 5.yield from的使用 6.yield ...

  6. python嗅探工具详解附源码(使用socket,带tkinter界面)

    python嗅探工具详解(带tkinter界面) 点击详见原理 点击详见原理 TCP/IP协议号补充 必备基础知识 IP数据包格式 详见点此 TCP报文格式 详见点此 struct模块 在Python ...

  7. Python开发技术详解

    Python开发技术详解 下载地址 https://pan.baidu.com/s/1KTrk3B1sajMiPIoo3-Rhuw 扫码下面二维码关注公众号回复 100089获取分享码 本书目录结构如 ...

  8. python开发技术详解pdf下载_python开发技术详解附源码-python开发技术详解电子书pdf下载高清去水印版-精品下载...

    Python开发技术详解适合Python爱好者.大中专院校的学生.社会培训班的学生以及用Python语言进行系统管理.GUI开发.Web开发.数据库编程.网络编程的人员使用. 内容提要 Python是 ...

  9. python协程详解_python协程详解

    原博文 2019-10-25 10:07 − # python协程详解 ![python协程详解](https://pic2.zhimg.com/50/v2-9f3e2152b616e89fbad86 ...

最新文章

  1. 近期必读的9篇CVPR 2019【域自适应(Domain Adaptation)】相关论文和代码
  2. appium 如何调用 adb_带你了解可用于Android APP自动化测试的框架:Appium
  3. 谈一谈对旋转矩阵的理解
  4. php 设为首页 收藏_如何在网站上添加“设为首页”“加入收藏”
  5. 2019蓝桥杯B组:完全二叉树权值
  6. 推荐3个好用的Excel项目管理甘特图模板
  7. java 获取本机地址_java如何获取本机IP地址
  8. 2018校招笔试真题汇总
  9. mysql快照数据_制作mysql数据快照
  10. 学期博客:学习进度条
  11. 3. 布莱叶盲文与二进制码
  12. c语言eval函数,百行代码轻便实现C#中的Eval函数
  13. 试用期、见习期、实习期、合同期、服务期的区别与应用
  14. c语言将一个字符串转置,c语言实现数组的转置
  15. 万圣节日丧尸变异头像生成流量主小程序开发
  16. Geek的卸载存在小小缺憾
  17. 银河麒麟系统中的串口调试
  18. ZLMediaKit源码分析 - NotifyCenter
  19. python数据分析处理库-Pandas
  20. 洛谷入门1【顺序结构】题单题解

热门文章

  1. 紫光展锐发布系统级安全的高性能5G SoC移动平台T820
  2. 雍正杀“舅”:握着领导把柄,隆科多必须死?
  3. macos 安装cms php,苹果cms安装及配置详细教程
  4. java isreachable_Java网络编程从入门到精通(12):使用isReachable方法探测主机是否可以连通...
  5. 计算机 无法进入睡眠模式,win10电脑无法进入睡眠模式怎么解决
  6. Docker基础(三)—配置镜像加速器
  7. linux 部署 程序,Linux 部署配置WEB APP
  8. 吴乙己的数仓指南_2维度建模核心思想
  9. 数数(数学题运算分配律)
  10. Vue+Vux实现登录