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 相关文章推荐
Python中MYSQLdb出现乱码的解决方法
Oct 11 Python
Python3读取Excel数据存入MySQL的方法
May 04 Python
Django uwsgi Nginx 的生产环境部署详解
Feb 02 Python
Python中使用pypdf2合并、分割、加密pdf文件的代码详解
May 21 Python
python urllib爬虫模块使用解析
Sep 05 Python
python反转列表的三种方式解析
Nov 08 Python
python tkinter之 复选、文本、下拉的实现
Mar 04 Python
Python pip安装模块提示错误解决方案
May 22 Python
解决pyinstaller 打包exe文件太大,用pipenv 缩小exe的问题
Jul 13 Python
Python GUI库Tkiner使用方法代码示例
Nov 27 Python
使用Python获取爱奇艺电视剧弹幕数据的示例代码
Jan 12 Python
python 求两个向量的顺时针夹角操作
Mar 04 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
使用php4加速网络传输
2006/10/09 PHP
实现php加速的eAccelerator dll支持文件打包下载
2007/09/30 PHP
PHP读取ACCESS数据到MYSQL的代码
2011/05/11 PHP
使用php+apc实现上传进度条且在IE7下不显示的问题解决方法
2013/04/25 PHP
ThinkPHP表单自动验证实例
2014/10/13 PHP
php防止恶意刷新与刷票的方法
2014/11/21 PHP
PHP+MySQL修改记录的方法
2015/01/21 PHP
Alliance vs AM BO3 第一场2.13
2021/03/10 DOTA
javascript获得CheckBoxList选中的数量
2009/10/27 Javascript
基于jquery实现点击左右按钮图片横向滚动
2013/04/11 Javascript
jQuery 获取、设置HTML或TEXT内容的两种方法
2014/05/23 Javascript
在JavaScript中操作数组之map()方法的使用
2015/06/09 Javascript
javascript中不易分清的slice,splice和split三个函数
2016/03/29 Javascript
node.js报错:Cannot find module 'ejs'的解决办法
2016/12/14 Javascript
layui前段框架日期控件使用方法详解
2017/05/19 Javascript
Angular实现响应式表单
2017/08/04 Javascript
WebStorm ES6 语法支持设置&amp;babel使用及自动编译(详解)
2017/09/08 Javascript
判断滚动条滑到底部触发事件(实例讲解)
2017/11/15 Javascript
关于vue单文件中引用路径的处理方法
2018/01/08 Javascript
JS中双击和单击事件冲突的解决方法
2018/04/09 Javascript
vue1.0和vue2.0的watch监听事件写法详解
2018/09/11 Javascript
js前端面试之同步与异步问题详解
2019/04/03 Javascript
JS关闭子窗口并且刷新上一个窗口的实现示例
2020/03/10 Javascript
[03:33]TI9战队采访 - Infamous
2019/08/20 DOTA
python实现爬虫下载美女图片
2015/07/14 Python
python写入已存在的excel数据实例
2018/05/03 Python
python3连接MySQL数据库实例详解
2018/05/24 Python
Python自动发送邮件的方法实例总结
2018/12/08 Python
Python3实现将一维数组按标准长度分隔为二维数组
2019/11/29 Python
让IE支持CSS3的不完全兼容方案
2014/09/19 HTML / CSS
Clearly新西兰:购买眼镜、太阳镜和隐形眼镜
2018/04/26 全球购物
澳大利亚第一旅行车和房车配件店:Caravan RV Camping
2020/12/26 全球购物
上海中网科技笔试题
2012/02/19 面试题
我的老师教学反思
2014/05/01 职场文书
个人承诺书怎么写
2014/05/24 职场文书
大雁塔导游词
2015/02/04 职场文书