python之多线程并发(python多线程并发执行)
wptr33 2025-05-11 01:43 18 浏览
前言
今天呢笔者想和大家来聊聊python多线程的并发,废话就不多说了咱们直接进入主题哟。
一、线程执行
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
我是一个线程函数 2022-06-23 16:54:58.205146
我是一个线程函数 2022-06-23 16:54:58.205146
我是一个线程函数 2022-06-23 16:54:58.206159
我是一个线程函数 2022-06-23 16:54:58.206159
我是一个线程函数 2022-06-23 16:54:58.206159
我是一个线程函数 2022-06-23 16:54:58.207139
我是一个线程函数 2022-06-23 16:54:58.207139
我是一个线程函数 2022-06-23 16:54:58.207139
我是一个线程函数 2022-06-23 16:54:58.208150
我是一个线程函数 2022-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 0500个线程共执行了大约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
2022-06-23 19:08:00.468612
2022-06-23 19:08:00.468612
2022-06-23 19:08:00.468612
2022-06-23 19:08:00.468612
2022-06-23 19:08:00.468612
2022-06-23 19:08:00.468612
2022-06-23 19:08:00.468612
2022-06-23 19:08:00.468612
2022-06-23 19:08:00.468612
2022-06-23 19:08:00.468612
2022-06-23 19:08:00.468612
2022-06-23 19:08:00.469559
2022-06-23 19:08:00.469559
2022-06-23 19:08:00.469559
2022-06-23 19:08:00.469559
2022-06-23 19:08:00.469559
2022-06-23 19:08:00.469559
2022-06-23 19:08:00.470556
2022-06-23 19:08:00.470556
2022-06-23 19:08:00.470556
2022-06-23 19:08:00.470556
2022-06-23 19:08:00.470556
2022-06-23 19:08:00.470556
2022-06-23 19:08:00.470556
2022-06-23 19:08:00.470556
2022-06-23 19:08:00.470556
2022-06-23 19:08:00.470556
2022-06-23 19:08:00.470556
2022-06-23 19:08:00.470556
2022-06-23 19:08:00.470556
2022-06-23 19:08:00.470556
2022-06-23 19:08:00.470556
2022-06-23 19:08:00.471554
2022-06-23 19:08:00.471554
2022-06-23 19:08:00.471554
2022-06-23 19:08:00.471554
2022-06-23 19:08:00.471554
2022-06-23 19:08:00.471554
2022-06-23 19:08:00.471554
2022-06-23 19:08:00.471554
2022-06-23 19:08:00.471554
2022-06-23 19:08:00.471554
2022-06-23 19:08:00.471554
2022-06-23 19:08:00.471554
2022-06-23 19:08:00.472557
2022-06-23 19:08:00.472557
2022-06-23 19:08:00.472557
2022-06-23 19:08:00.472557
2022-06-23 19:08:00.472557
2022-06-23 19:08:00.472557
2022-06-23 19:08:00.472557
2022-06-23 19:08:00.472557
2022-06-23 19:08:00.472557
2022-06-23 19:08:00.472557
2022-06-23 19:08:00.472557
2022-06-23 19:08:00.472557
2022-06-23 19:08:00.472557
2022-06-23 19:08:00.472557
2022-06-23 19:08:00.472557
2022-06-23 19:08:00.473548
2022-06-23 19:08:00.473548
2022-06-23 19:08:00.473548
2022-06-23 19:08:00.473548
2022-06-23 19:08:00.473548
2022-06-23 19:08:00.473548
2022-06-23 19:08:00.473548
2022-06-23 19:08:00.473548
2022-06-23 19:08:00.473548
2022-06-23 19:08:00.473548
2022-06-23 19:08:00.473548
2022-06-23 19:08:00.473548
2022-06-23 19:08:00.473548
2022-06-23 19:08:00.474545
2022-06-23 19:08:00.474545
2022-06-23 19:08:00.474545
2022-06-23 19:08:00.474545
2022-06-23 19:08:00.474545
2022-06-23 19:08:00.474545
2022-06-23 19:08:00.474545
2022-06-23 19:08:00.475552
2022-06-23 19:08:00.475552
2022-06-23 19:08:00.475552
2022-06-23 19:08:00.475552
2022-06-23 19:08:00.475552
2022-06-23 19:08:00.475552
2022-06-23 19:08:00.475552
2022-06-23 19:08:00.475552
2022-06-23 19:08:00.475552
2022-06-23 19:08:00.476548
2022-06-23 19:08:00.476548
2022-06-23 19:08:00.476548
2022-06-23 19:08:00.476548
2022-06-23 19:08:00.476548
2022-06-23 19:08:00.476548
2022-06-23 19:08:00.476548
2022-06-23 19:08:00.476548
2022-06-23 19:08:00.476548
2022-06-23 19:08:00.476548
2022-06-23 19:08:00.477546
2022-06-23 19:08:00.477546
2022-06-23 19:08:00.477546
2022-06-23 19:08:00.477546
2022-06-23 19:08:00.477546
2022-06-23 19:08:00.477546
2022-06-23 19:08:00.477546
2022-06-23 19:08:00.477546
2022-06-23 19:08:00.477546
2022-06-23 19:08:00.477546
2022-06-23 19:08:00.477546
2022-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")输出结果
2022-06-23 19:12:35.564539
2022-06-23 19:12:35.564539
2022-06-23 19:12:35.564539
2022-06-23 19:12:35.564539
2022-06-23 19:12:35.564539
2022-06-23 19:12:35.564539
2022-06-23 19:12:35.565529
2022-06-23 19:12:35.565529
2022-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” 然后结束程序执行, 这就是阻塞线程的意义,控制子线程和主线程的执行顺序
总结
最好呢,再次说一下守护线程和阻塞线程的定义
守护线程:子线程会随着主线程的结束而结束,无论子线程是否执行完毕
阻塞线程:主线程会等待子线程的执行结束,才继续执行
最后今天的文章就到这里了哟,喜欢的小伙伴可以点赞收藏评论关注哟。
相关推荐
- oracle数据导入导出_oracle数据导入导出工具
-
关于oracle的数据导入导出,这个功能的使用场景,一般是换服务环境,把原先的oracle数据导入到另外一台oracle数据库,或者导出备份使用。只不过oracle的导入导出命令不好记忆,稍稍有点复杂...
- 继续学习Python中的while true/break语句
-
上次讲到if语句的用法,大家在微信公众号问了小编很多问题,那么小编在这几种解决一下,1.else和elif是子模块,不能单独使用2.一个if语句中可以包括很多个elif语句,但结尾只能有一个...
- python continue和break的区别_python中break语句和continue语句的区别
-
python中循环语句经常会使用continue和break,那么这2者的区别是?continue是跳出本次循环,进行下一次循环;break是跳出整个循环;例如:...
- 简单学Python——关键字6——break和continue
-
Python退出循环,有break语句和continue语句两种实现方式。break语句和continue语句的区别:break语句作用是终止循环。continue语句作用是跳出本轮循环,继续下一次循...
- 2-1,0基础学Python之 break退出循环、 continue继续循环 多重循
-
用for循环或者while循环时,如果要在循环体内直接退出循环,可以使用break语句。比如计算1至100的整数和,我们用while来实现:sum=0x=1whileTrue...
- Python 中 break 和 continue 傻傻分不清
-
大家好啊,我是大田。...
- python中的流程控制语句:continue、break 和 return使用方法
-
Python中,continue、break和return是控制流程的关键语句,用于在循环或函数中提前退出或跳过某些操作。它们的用途和区别如下:1.continue(跳过当前循环的剩余部分,进...
- L017:continue和break - 教程文案
-
continue和break在Python中,continue和break是用于控制循环(如for和while)执行流程的关键字,它们的作用如下:1.continue:跳过当前迭代,...
- 作为前端开发者,你都经历过怎样的面试?
-
已经裸辞1个月了,最近开始投简历找工作,遇到各种各样的面试,今天分享一下。其实在职的时候也做过面试官,面试官时,感觉自己问的问题很难区分候选人的能力,最好的办法就是看看候选人的github上的代码仓库...
- 面试被问 const 是否不可变?这样回答才显功底
-
作为前端开发者,我在学习ES6特性时,总被const的"善变"搞得一头雾水——为什么用const声明的数组还能push元素?为什么基本类型赋值就会报错?直到翻遍MDN文档、对着内存图反...
- 2023金九银十必看前端面试题!2w字精品!
-
导文2023金九银十必看前端面试题!金九银十黄金期来了想要跳槽的小伙伴快来看啊CSS1.请解释CSS的盒模型是什么,并描述其组成部分。...
- 前端面试总结_前端面试题整理
-
记得当时大二的时候,看到实验室的学长学姐忙于各种春招,有些收获了大厂offer,有些还在苦苦面试,其实那时候的心里还蛮忐忑的,不知道自己大三的时候会是什么样的一个水平,所以从19年的寒假放完,大二下学...
- 由浅入深,66条JavaScript面试知识点(七)
-
作者:JakeZhang转发链接:https://juejin.im/post/5ef8377f6fb9a07e693a6061目录...
- 2024前端面试真题之—VUE篇_前端面试题vue2020及答案
-
添加图片注释,不超过140字(可选)...
- 今年最常见的前端面试题,你会做几道?
-
在面试或招聘前端开发人员时,期望、现实和需求之间总是存在着巨大差距。面试其实是一个交流想法的地方,挑战人们的思考方式,并客观地分析给定的问题。可以通过面试了解人们如何做出决策,了解一个人对技术和解决问...
- 一周热门
- 最近发表
-
- oracle数据导入导出_oracle数据导入导出工具
- 继续学习Python中的while true/break语句
- python continue和break的区别_python中break语句和continue语句的区别
- 简单学Python——关键字6——break和continue
- 2-1,0基础学Python之 break退出循环、 continue继续循环 多重循
- Python 中 break 和 continue 傻傻分不清
- python中的流程控制语句:continue、break 和 return使用方法
- L017:continue和break - 教程文案
- 作为前端开发者,你都经历过怎样的面试?
- 面试被问 const 是否不可变?这样回答才显功底
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)
