百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

python之多线程并发

moboyou 2025-06-07 16:56 9 浏览

前言

今天呢笔者想和大家来聊聊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 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
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” 然后结束程序执行, 这就是阻塞线程的意义,控制子线程和主线程的执行顺序

总结

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

守护线程:子线程会随着主线程的结束而结束,无论子线程是否执行完毕

阻塞线程:主线程会等待子线程的执行结束,才继续执行

最后今天的文章就到这里了哟,喜欢的小伙伴可以点赞收藏评论关注哟。

相关推荐

免费主机|永久免费空间|php虚拟主机|博客主机|论坛主机|免费域名

免费主机|永久免费空间|php虚拟主机|博客主机|论坛主机|免费域名|云主机在出教程之前准备好久,测试搭建轻量论坛无压力选用稳定免费域名免费主机分销给,可以套CDN使用坚持免费时间是大厂不能媲美,刚开...

.NET和Blazor WebAssembly 轻量级博客

简介Blogifier是一个用ASP编写的自托管开源发布平台。.NET和BlazorWebAssembly。它可以用来快速,轻松地建立一个轻量级的,但功能齐全的个人或团体博客。截图支持md教程如果...

等了30年,微软MS-DOS神器重生:用Rust重写、开源斩获9.9k Star、还能跑在Linux上!

整理|苏宓出品|CSDN(ID:CSDNnews)曾经称“开源是毒瘤”时有多么嫌弃,现在“微软开源”就有多么热烈,甚至舍得把很多经典的系统、项目都逐步开源出来。回看过去两年间,微软先是开源...

教程 | 一文搭建你的第一个免费专属博客

我建了一个QQ学习交流群,旨在“分享、讨论、学习、资源分享、就业机会、互联网内推、共同进步!”,感兴趣的可以加一下,也可以添加我的QQ~QQ群:1002821945;QQ号:498073774;前言...

YzmCMS是一款基于YZMPHP开发的一套轻量级开源内容管理系统

YzmCMS是一款基于YZMPHP开发的一套轻量级开源内容管理系统,YzmCMS简洁、安全、开源、实用,可运行在Linux、Windows、MacOSX、Solaris等各种平台上,专注为公司企业、个...

PyPoster, 轻量级的博客发布小工具

引言PyPoster是一个采用Python3.5编写的博客离线发布小工具,GUI采用tkinter框架构建。理论上,可以在安装了Python运行环境的多种平台下使用它。PyPoster目前...

Java和前端哪个更累?(java与前端哪个更推荐)

一、首先前后端开发各是什么?1.前端开发:网站的“前端”是与用户直接交互的部分,包括你在浏览网页时接触的所有视觉内容--从字体到颜色,以及下拉菜单和侧边栏。这些视觉内容,都是由浏览器解析、处理、渲染相...

Linux系统区别英文字母大小写(linux的命令是否区分大小写)

我们一般在Windows系统开发程序并进行功能测试,如果上线的时候选择Windows服务器的话,是什么问题都没有。但是当选择Linux系统的时候,就必须注意Linux系统是严格的区别文字大小。Wind...

原创:带你全面了解和学习PHP(php学的是什么)

PHP能做什么?学习PHP,你应该感到幸运,因为如果你学过其他语言,你就会发现PHP还是相对简单的,如果是初学阶段,你要搞清楚HTML和PHP的概念,之后你完全可以让PHP给你算算一加一等于几,然后在...

我把 Mac mini 托管到机房了:一套打败云服务器的终极方案

本内容来源于@什么值得买APP,观点仅代表作者本人|作者:薯仔不爱吃薯仔我把我积灰的Macmini托管到机房了,有图有真相。虽然画质又渣又昏暗,但是!这就是实锤。作为开发者,谁不想拥有个自己的服...

PHP技能评测(php认证考试)

公司出了一些自我评测的PHP题目,现将题目和答案记录于此,以方便记忆。1.魔术函数有哪些,分别在什么时候调用?__construct(),类的构造函数__destruct(),类的析构函数__cal...

PHP的相似性和差异Ruby ON Rails,Python

就像我们所说的语言是唯一的不同,编程语言也有变化,从知名度、可用性和可靠性。每一种语言都有不同方面的用途。之间的主要相似PHP,RubyonRails和Python是他们都是动态的面向对象的语言。...

查看WordPress站点查询缓慢问题并进行优化教程

大家都知道WordPress是个需要大量查询的程序,查询越多,WordPress网站越慢,如何优化WordPress查询呢?这里我们需要用到QueryMonitor插件,也就是查询监视器插件。在本教...

go 和 php 性能如何进行对比?(go php7 对比)

PHP性能很差吗?每次讲到PHP和其他语言间的性能对比,似乎都会发现这样一个声音:单纯的性能对比没有意义,主要瓶颈首先是数据库,其次是业务代码等等。好像PHP的性能真的不能单独拿出来讨论似的。但其实一...

PHP在做爬虫时的解决方案(php实现爬虫)

爬虫不是一个小众的场景,所以无论是哪个语言,都有很多相应的生态库.这里介绍一下PHP的技术方案和代码量。关键能力对页面的解析能力PHP的官方扩展中有Dom扩展,但是我建议使用electrolinux/...