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 相关文章推荐
Python3 入门教程 简单但比较不错
Nov 29 Python
Python实现测试磁盘性能的方法
Mar 12 Python
python实现简单的计时器功能函数
Mar 14 Python
python处理二进制数据的方法
Jun 03 Python
Python使用flask框架操作sqlite3的两种方式
Jan 31 Python
windows10下python3.5 pip3安装图文教程
Apr 02 Python
python 输入一个数n,求n个数求乘或求和的实例
Nov 13 Python
Python实现将多个空格换为一个空格.md的方法
Dec 20 Python
在python image 中实现安装中文字体
May 16 Python
Python celery原理及运行流程解析
Jun 13 Python
Numpy 多维数据数组的实现
Jun 18 Python
python not运算符的实例用法
Jun 30 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
php 中的4种标记风格介绍
2012/05/10 PHP
解析php dirname()与__FILE__常量的应用
2013/06/24 PHP
PHP 使用redis简单示例分享
2015/03/05 PHP
PHP基于正则批量替换Img中src内容实现获取缩略图的功能示例
2017/06/07 PHP
雄兵连第三季海报曝光,艾妮熙德成主角,蔷薇新造型
2021/03/09 国漫
javascript CSS画图之基础篇
2009/07/29 Javascript
Javascript 继承实现例子
2009/08/12 Javascript
extjs 的权限问题 要求控制的对象是 菜单,按钮,URL
2010/03/09 Javascript
jquery封装的对话框简单实现
2013/07/21 Javascript
JS实现根据出生年月计算年龄
2014/01/10 Javascript
如何将网页表格内容导入excel
2014/02/18 Javascript
JavaSciprt中处理字符串之sup()方法的使用教程
2015/06/08 Javascript
JS非Alert实现网页右下角“未读信息”效果弹窗
2015/09/26 Javascript
全面解析Bootstrap表单使用方法(表单控件状态)
2015/11/24 Javascript
JS与jQuery实现隔行变色的方法
2016/09/09 Javascript
vue实现添加标签demo示例代码
2017/01/21 Javascript
javascript原型链学习记录之继承实现方式分析
2019/05/01 Javascript
vue穿梭框实现上下移动
2021/01/29 Vue.js
理解python多线程(python多线程简明教程)
2014/06/09 Python
python实现登陆知乎获得个人收藏并保存为word文件
2015/03/16 Python
用Python删除本地目录下某一时间点之前创建的所有文件的实例
2017/12/14 Python
Python实现将Excel转换成为image的方法
2018/10/23 Python
python 通过可变参数计算n个数的乘积方法
2019/06/13 Python
澳大利亚票务和娱乐市场领导者:Ticketmaster
2017/03/03 全球购物
亿阳信通股份有限公司笔试题(C#)
2016/03/04 面试题
选秀节目策划方案
2014/06/06 职场文书
七一建党日演讲稿
2014/09/05 职场文书
全陪导游词
2015/02/04 职场文书
材料采购员岗位职责
2015/04/03 职场文书
办公用品管理制度
2015/08/04 职场文书
情况说明书格式及范文
2019/06/24 职场文书
详解CSS玩转图片Base64编码
2021/05/25 HTML / CSS
K8s部署发布Golang应用程序的实现方法
2021/07/16 Golang
大型强子对撞机再次重启探索“第五种自然力”
2022/04/29 数码科技
Android存储中最基本的文件存储方式
2022/04/30 Java/Android
利用Python实现翻译HTML中的文本字符串
2022/06/21 Python