python多线程并发实例及其优化


Posted in Python onJune 27, 2019

单线程执行

python的内置模块提供了两个内置模块:thread和threading,thread是源生模块,threading是扩展模块,在thread的基础上进行了封装及改进。所以只需要使用threading这个模块就能完成并发的测试

实例

创建并启动一个单线程

import threading
def myTestFunc():
print("我是一个函数")
t = threading.Thread(target=myTestFunc) # 创建一个线程
t.start() # 启动线程

执行结果

C:\Python36\python.exe D:/MyThreading/myThread.py
我是一个线程函数
Process finished with exit code 0

其实单线程的执行结果和单独执行某一个或者某一组函数结果是一样的,区别只在于用线程的方式执行函数,而线程是可以同时执行多个的,函数是不可以同时执行的。

多线程执行

上面介绍了单线程如何使用,多线程只需要通过循环创建多个线程,并循环启动线程执行就可以了

实例

import threading
from datetime import datetime
def thread_func(): # 线程函数
  print('我是一个线程函数', datetime.now())
def many_thread():
  threads = []
  for _ in range(10): # 循环创建10个线程
    t = threading.Thread(target=thread_func)
    threads.append(t)
  for t in threads: # 循环启动10个线程
    t.start()
if __name__ == '__main__':
  many_thread()

执行结果

C:\Python36\python.exe D:/MyThreading/manythread.py
我是一个线程函数 2019-06-23 16:54:58.205146
我是一个线程函数 2019-06-23 16:54:58.205146
我是一个线程函数 2019-06-23 16:54:58.206159
我是一个线程函数 2019-06-23 16:54:58.206159
我是一个线程函数 2019-06-23 16:54:58.206159
我是一个线程函数 2019-06-23 16:54:58.207139
我是一个线程函数 2019-06-23 16:54:58.207139
我是一个线程函数 2019-06-23 16:54:58.207139
我是一个线程函数 2019-06-23 16:54:58.208150
我是一个线程函数 2019-06-23 16:54:58.208150
Process finished with exit code 0

通过循环创建10个线程,并且执行了10次线程函数,但需要注意的是python的并发并非绝对意义上的同时处理,因为启动线程是通过循环启动的,还是有先后顺序的,通过执行结果的时间可以看出还是有细微的差异,但可以忽略不记。当然如果线程过多就会扩大这种差异。我们启动500个线程看下程序执行时间

实例

import threading
from datetime import datetime
def thread_func(): # 线程函数
print('我是一个线程函数', datetime.now())
def many_thread():
threads = []
for _ in range(500): # 循环创建500个线程
t = threading.Thread(target=thread_func)
threads.append(t)
for t in threads: # 循环启动500个线程
t.start()
if __name__ == '__main__':
start = datetime.today().now()
many_thread()
duration = datetime.today().now() - start
print(duration)

执行结果

0:00:00.111657
Process finished with exit code 0

500个线程共执行了大约0.11秒

那么针对这种问题我们该如何优化呢?我们可以创建25个线程,每个线程执行20次线程函数,这样在启动下一个线程的时候,上一个线程已经在循环执行了,这样就大大减少了并发的时间差异

优化

import threading
from datetime import datetime
def thread_func(): # 线程函数
print('我是一个线程函数', datetime.now())
def execute_func():
for _ in range(20):
thread_func()
def many_thread():
start = datetime.now()
threads = []
for _ in range(25): # 循环创建500个线程
t = threading.Thread(target=execute_func)
threads.append(t)
for t in threads: # 循环启动500个线程
t.start()
duration = datetime.now() - start
print(duration)
if __name__ == '__main__':
many_thread()

输出结果(仅看程序执行间隔)

0:00:00.014959
Process finished with exit code 0

后面的优化执行500次并发一共花了0.014秒。比未优化前的500个并发快了几倍,如果线程函数的执行时间比较长的话,那么这个差异会更加显著,所以大量的并发测试建议使用后者,后者比较接近同时“并发”

守护线程

多线程还有一个重要概念就是守护线程。那么在这之前我们需要知道主线程和子线程的区别,之前创建的线程其实都是main()线程的子线程,即先启动主线程main(),然后执行线程函数子线程。

那么什么是守护线程?即当主线程执行完毕之后,所有的子线程也被关闭(无论子线程是否执行完成)。默认不设置的情况下是没有守护线程的,主线程执行完毕后,会等待子线程全部执行完毕,才会关闭结束程序。

但是这样会有一个弊端,当子线程死循环了或者一直处于等待之中,则程序将不会被关闭,被被无限挂起,我们把上述的线程函数改成循环10次, 并睡眠2秒,这样效果会更明显

import threading
from datetime import datetime
import time
def thread_func(): # 线程函数
 time.sleep(2)
i = 0
while(i < 11):
print(datetime.now())
i += 1
def many_thread():
threads = []
for _ in range(10): # 循环创建500个线程
t = threading.Thread(target=thread_func)
threads.append(t)
for t in threads: # 循环启动500个线程
t.start()
if __name__ == '__main__':
many_thread()
print("thread end")

执行结果

C:\Python36\python.exe D:/MyThreading/manythread.py
thread end
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.469559
2019-06-23 19:08:00.469559
2019-06-23 19:08:00.469559
2019-06-23 19:08:00.469559
2019-06-23 19:08:00.469559
2019-06-23 19:08:00.469559
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.474545
2019-06-23 19:08:00.474545
2019-06-23 19:08:00.474545
2019-06-23 19:08:00.474545
2019-06-23 19:08:00.474545
2019-06-23 19:08:00.474545
2019-06-23 19:08:00.474545
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
Process finished with exit code 0

根据上述结果可以看到主线程打印了“thread end”之后(主线程结束),子线程还在继续执行,并未随着主线程的结束而结束

下面我们通过 setDaemon方法给子线程添加守护线程,我们把循环改为死循环,再来看看输出结果(注意守护线程要加在start之前)

import threading
from datetime import datetime
def thread_func(): # 线程函数
i = 0
while(1):
print(datetime.now())
i += 1
def many_thread():
threads = []
for _ in range(10): # 循环创建500个线程
t = threading.Thread(target=thread_func)
threads.append(t)
t.setDaemon(True) # 给每个子线程添加守护线程
for t in threads: # 循环启动500个线程
t.start()

if __name__ == '__main__':
many_thread()
print("thread end")

输出结果

2019-06-23 19:12:35.564539
2019-06-23 19:12:35.564539
2019-06-23 19:12:35.564539
2019-06-23 19:12:35.564539
2019-06-23 19:12:35.564539
2019-06-23 19:12:35.564539
2019-06-23 19:12:35.565529
2019-06-23 19:12:35.565529
2019-06-23 19:12:35.565529
thread end
Process finished with exit code 0

通过结果我们可以发现,主线程关闭之后子线程也会随着关闭,并没有无限的循环下去,这就像程序执行到一半强制关闭执行一样,看似暴力却很有用,如果子线程发送一个请求未收到请求结果,那不可能永远等下去,这时候就需要强制关闭。所以守护线程解决了主线程和子线程关闭的问题。

阻塞线程

上面说了守护线程的作用,那么有没有别的方法来解决上述问题呢? 其实是有的,那就是阻塞线程,这种方式更加合理,使用join()方法阻塞线程,让主线程等待子线程执行完成之后再往下执行,再关闭所有子线程,而不是只要主线程结束,不管子线程是否执行完成都终止子线程执行。下面我们给子线程添加上join()(主要join要加到start之后)

import threading
from datetime import datetime
import time
def thread_func(): # 线程函数
time.sleep(1)
i = 0
while(i < 11):
print(datetime.now())
i += 1
def many_thread():
threads = []
for _ in range(10): # 循环创建500个线程
t = threading.Thread(target=thread_func)
threads.append(t)
t.setDaemon(True) # 给每个子线程添加守护线程
for t in threads: # 循环启动500个线程
t.start()
for t in threads:
t.join() # 阻塞线程
if __name__ == '__main__':
many_thread()
print("thread end")

执行结果

程序会一直执行,但是不会打印“thread end”语句,因为子线程并未结束,那么主线程就会一直等待。

疑问:有人会觉得这和什么都不设置是一样的,其实会有一点区别的,从守护线程和线程阻塞的定义就可以看出来,如果什么都没设置,那么主线程会先执行完毕打印后面的“thread end”,而等待子线程执行完毕。两个都设置了,那么主线程会等待子线程执行结束再继续执行。

而对于死循环或者一直等待的情况,我们可以给join设置超时等待,我们设置join的参数为2,那么子线程会告诉主线程让其等待2秒,如果2秒内子线程执行结束主线程就继续往下执行,如果2秒内子线程未结束,主线程也会继续往下执行,执行完成后关闭子线程

输出结果

import threading
from datetime import datetime
import time
def thread_func(): # 线程函数
time.sleep(1)
i = 0
while(1):
print(datetime.now())
i += 1
def many_thread():
threads = []
for _ in range(10): # 循环创建500个线程
t = threading.Thread(target=thread_func)
threads.append(t)
t.setDaemon(True) # 给每个子线程添加守护线程
for t in threads: # 循环启动500个线程
t.start()
for t in threads:
t.join(2) # 设置子线程超时2秒
if __name__ == '__main__':
many_thread()
print("thread end")

你运行程序后会发现,运行了大概2秒的时候,程序会数据“thread end” 然后结束程序执行, 这就是阻塞线程的意义,控制子线程和主线程的执行顺序

总结

最好呢,再次说一下守护线程和阻塞线程的定义

  • 守护线程:子线程会随着主线程的结束而结束,无论子线程是否执行完毕
  • 阻塞线程:主线程会等待子线程的执行结束,才继续执行

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

Python 相关文章推荐
用map函数来完成Python并行任务的简单示例
Apr 02 Python
Python中字符串对齐方法介绍
May 21 Python
利用Python命令行传递实例化对象的方法
Nov 02 Python
Python实现mysql数据库更新表数据接口的功能
Nov 19 Python
pycharm远程调试openstack代码
Nov 21 Python
利用Python+Java调用Shell脚本时的死锁陷阱详解
Jan 24 Python
Django跨域请求CSRF的方法示例
Nov 11 Python
基于多进程中APScheduler重复运行的解决方法
Jul 22 Python
详解Python中namedtuple的使用
Apr 27 Python
完美解决python针对hdfs上传和下载的问题
Jun 05 Python
python3.7调试的实例方法
Jul 21 Python
Python使用lambda抛出异常实现方法解析
Aug 20 Python
int在python中的含义以及用法
Jun 27 #Python
Pycharm运行加载文本出现错误的解决方法
Jun 27 #Python
基于python-opencv3的图像显示和保存操作
Jun 27 #Python
pycharm new project变成灰色的解决方法
Jun 27 #Python
python之mock模块基本使用方法详解
Jun 27 #Python
python文件选择对话框的操作方法
Jun 27 #Python
python开启debug模式的方法
Jun 27 #Python
You might like
支持生僻字且自动识别utf-8编码的php汉字转拼音类
2014/06/27 PHP
PHP使用mysql_fetch_object从查询结果中获取对象集的方法
2015/03/18 PHP
在Debian系统下配置LNMP的教程
2015/07/09 PHP
从JavaScript的函数重名看其初始化方式
2007/03/08 Javascript
js Event对象的5种坐标
2011/09/12 Javascript
js拦截alert对话框另类应用
2013/01/16 Javascript
jQuery实现dialog设置focus焦点的方法
2015/06/10 Javascript
使用Raygun对Node.js应用进行错误处理的方法
2015/06/23 Javascript
JS实现页面载入时随机显示图片效果
2016/09/07 Javascript
jQuery之动画效果大全
2016/11/09 Javascript
JS实现的全排列组合算法示例
2017/10/09 Javascript
基于Vue框架vux组件库实现上拉刷新功能
2017/11/28 Javascript
vue element自定义表单验证请求后端接口验证
2019/12/11 Javascript
[04:26]2014DOTA2国际邀请赛-Newbee顺利进入胜者组决赛 独家专访战神7
2014/07/19 DOTA
[50:38]DOTA2-DPC中国联赛 正赛 Phoenix vs CDEC BO3 第二场 3月7日
2021/03/11 DOTA
python构造icmp echo请求和实现网络探测器功能代码分享
2014/01/10 Python
Python实现端口复用实例代码
2014/07/03 Python
python实现的生成随机迷宫算法核心代码分享(含游戏完整代码)
2014/07/11 Python
python executemany的使用及注意事项
2017/03/13 Python
为什么str(float)在Python 3中比Python 2返回更多的数字
2018/10/16 Python
Python的Tkinter点击按钮触发事件的例子
2019/07/19 Python
Python+Selenium实现自动化的环境搭建的步骤(图文)
2020/09/01 Python
Django与AJAX实现网页动态数据显示的示例代码
2021/02/24 Python
CSS3正方体旋转示例代码
2013/08/08 HTML / CSS
css3 给页面加个半圆形导航条主要利用旋转和倾斜样式
2014/02/10 HTML / CSS
深入解析HTML5中的Blob对象的使用
2015/09/08 HTML / CSS
Baby Tulai澳大利亚:美国婴儿背带品牌
2018/10/15 全球购物
美国帽子俱乐部商店:Hat Club
2019/07/05 全球购物
马来西亚在线购物市场:PGMall.my
2019/10/13 全球购物
如何设置Java的运行环境
2013/04/05 面试题
美发店5.1活动方案
2014/01/24 职场文书
党员政治学习材料
2014/05/14 职场文书
房地产开发项目建议书
2014/05/16 职场文书
2014年小学少先队工作总结
2014/12/18 职场文书
2015年体育部工作总结
2015/04/02 职场文书
CSS实现隐藏搜索框功能(动画正反向序列)
2021/07/21 HTML / CSS