Python多线程编程(一):threading 模块 Thread 类的用法详解_python threading-程序员宅基地

技术标签: Python 基本功  多线程  threading  Python  daemon  

我们进行程序开发的时候,肯定避免不了要处理并发的情况。

一般并发的手段有采用多进程和多线程。

但线程比进程更轻量化,系统开销一般也更低,所以大家更倾向于用多线程的方式处理并发的情况。

Python 提供多线程编程的方式。

本文基于 Python3 讲解,Python 实现多线程编程需要借助于 threading 模块。

所以,我们要在代码中引用它。

import threading

threading 模块中最核心的内容是 Thread 这个类。

我们要创建 Thread 对象,然后让它们运行,每个 Thread 对象代表一个线程,在每个线程中我们可以让程序处理不同的任务,这就是多线程编程。

值得注意的是,程序运行时默认就是在主线程上

创建 Thread 对象有 2 种手段。

  1. 直接创建 Thread ,将一个 callable 对象从类的构造器传递进去,这个 callable 就是回调函数,用来处理任务。
  2. 编写一个自定义类继承 Thread,然后复写 run() 方法,在 run() 方法中编写任务处理代码,然后创建这个 Thread 的子类。

1. 直接创建 Thread 对象。

class threading.Thread(group=None, target=None, name=None, args=(), kwargs={
    }, *, daemon=None)

Thread 的构造方法中,最重要的参数是 target,所以我们需要将一个 callable 对象赋值给它,线程才能正常运行。

如果要让一个 Thread 对象启动,调用它的 start() 方法就好了。

下面是代码示例。

import threading
import time

def test():

    for i in range(5):
        print('test ',i)
        time.sleep(1)


thread = threading.Thread(target=test)
thread.start()

for i in range(5):
    print('main ', i)
    time.sleep(1)

上面代码很简单,在主线程上打印 5 次,在一个子线程上打印 5 次。

运行结果如下:

test  0
main  0
main  1
test  1
main  2
test  2
main  3
test  3
main  4
test  4

上面的 callable 没有参数,如果需要传递参数的话,args 是固定参数,kwargs 是可变参数。

Thread 的名字

每一个 Thread 都有一个 name 的属性,代表的就是线程的名字,这个可以在构造方法中赋值。

如果在构造方法中没有个 name 赋值的话,默认就是 “Thread-N” 的形式,N 是数字。

import threading
import time

def test():

    for i in range(5):
        print(threading.current_thread().name+' test ',i)
        time.sleep(1)


thread = threading.Thread(target=test)
thread.start()

for i in range(5):
    print(threading.current_thread().name+' main ', i)
    time.sleep(1)

通过 thread.current_thread() 方法可以返回线程本身,然后就可以访问它的 name 属性。

上面代码运行结果如下:

Thread-1 test  0
MainThread main  0
Thread-1 test  1
MainThread main  1
Thread-1 test  2
MainThread main  2
Thread-1 test  3
MainThread main  3
Thread-1 test  4
MainThread main  4

如果我们在 Thread 对象创建时,构造方法里面赋值。

thread = threading.Thread(target=test,name='TestThread')

那么,运行结果会变成这个样子。

TestThread test  0
MainThread main  0
MainThread main  1
TestThread test  1
MainThread main  2
TestThread test  2
MainThread main  3
TestThread test  3
MainThread main  4
TestThread test  4

Thread 的生命周期

  1. 创建对象时,代表 Thread 内部被初始化。
  2. 调用 start() 方法后,thread 会开始运行。
  3. thread 代码正常运行结束或者是遇到异常,线程会终止。

可以通过 Thread 的 is_alive() 方法查询线程是否还在运行。

值得注意的是,is_alive() 返回 True 的情况是 Thread 对象被正常初始化,start() 方法被调用,然后线程的代码还在正常运行。

import threading
import time

def test():

    for i in range(5):
        print(threading.current_thread().name+' test ',i)
        time.sleep(0.5)


thread = threading.Thread(target=test,name='TestThread')
# thread = threading.Thread(target=test)
thread.start()

for i in range(5):
    print(threading.current_thread().name+' main ', i)
    print(thread.name+' is alive ', thread.isAlive())
    time.sleep(1)

在上面的代码中,我让 TestThread 比 MainThread 早一点结束,代码运行结果如下。

TestThread test  0
MainThread main  0
TestThread is alive  True
TestThread test  1
MainThread main  1
TestThread is alive  True
TestThread test  2
TestThread test  3
MainThread main  2
TestThread is alive  True
TestThread test  4
MainThread main  3
TestThread is alive  False
MainThread main  4
TestThread is alive  False

我们可以看到,主线程通过调用 TestThread 的 isAlive() 方法,准确查询到了它的存货状态。

join() 提供线程阻塞手段。

上面代码两个线程是同时运行的,但如果让一个先运行,一个后运行,怎么做呢?

调用一个 Thread 的 join() 方法,可以阻塞自身所在的线程。

import threading
import time

def test():

    for i in range(5):
        print(threading.current_thread().name+' test ',i)
        time.sleep(0.5)


thread = threading.Thread(target=test,name='TestThread')
thread.start()
thread.join()

for i in range(5):
    print(threading.current_thread().name+' main ', i)
    print(thread.name+' is alive ', thread.isAlive())
    time.sleep(1)

主线程创建了 TestThread 对象后,让其 start,然后通过调用 join() 方法,实现等待。程序运行结果如下:

TestThread test  0
TestThread test  1
TestThread test  2
TestThread test  3
TestThread test  4
MainThread main  0
TestThread is alive  False
MainThread main  1
TestThread is alive  False
MainThread main  2
TestThread is alive  False
MainThread main  3
TestThread is alive  False
MainThread main  4
TestThread is alive  False

默认的情况是,join() 会一直等待对应线程的结束,但可以通过参数赋值,等待规定的时间就好了。

def join(self, timeout=None):

timeout 是一个浮点参数,单位是秒。

如果我们更改上面的代码。

thread.join(1.0)

它的结果会是这样。

TestThread test  0
TestThread test  1
MainThread main  0
TestThread is alive  True
TestThread test  2
TestThread test  3
MainThread main  1
TestThread is alive  True
TestThread test  4
MainThread main  2
TestThread is alive  False
MainThread main  3
TestThread is alive  False
MainThread main  4
TestThread is alive  False

主线程只等待了 1 秒钟。

Thread 中的 daemon 属性

有同学可能会注意到,Thread 的构造方法中有一个 daemon 参数。默认是 None。

那么,daemon 起什么作用呢?

我们先看一段示例代码。

import threading
import time

def test():

    for i in range(5):
        print(threading.current_thread().name+' test ',i)
        time.sleep(2)


thread = threading.Thread(target=test,name='TestThread')
# thread = threading.Thread(target=test,name='TestThread',daemon=True)
thread.start()


for i in range(5):
    print(threading.current_thread().name+' main ', i)
    print(thread.name+' is alive ', thread.isAlive())
    time.sleep(1)

我们让主线程执行代码的时长比 TestThread 要短。

程序运行结果如下。

TestThread test  0
MainThread main  0
TestThread is alive  True
MainThread main  1
TestThread is alive  True
TestThread test  1
MainThread main  2
TestThread is alive  True
MainThread main  3
TestThread is alive  True
TestThread test  2
MainThread main  4
TestThread is alive  True
TestThread test  3
TestThread test  4

MainThread 没有代码运行的时候,TestThread 还在运行。

这是因为 MainThread 在等待其他线程的结束。

TestThread 中 daemon 属性默认是 False,这使得 MainThread 需要等待它的结束,自身才结束。

如果要达到,MainThread 结束,子线程也立马结束,怎么做呢?

其实很简单,只需要在子线程调用 start() 方法之前设置 daemon 就好了。

当然也可以在子线程的构造器中传递 daemon 的值为 True。

thread = threading.Thread(target=test,name='TestThread',daemon=True)
# thread.setDaemon(True)

更改前面代码示例,运行结果如下

TestThread test  0
MainThread main  0
TestThread is alive  True
MainThread main  1
TestThread is alive  True
TestThread test  1
MainThread main  2
TestThread is alive  True
MainThread main  3
TestThread is alive  True
TestThread test  2
MainThread main  4
TestThread is alive  True

可以看到 MainThread 结束了 TestThread 也结束了。

2.自定义类继承 Thread

前面讲过,直接初始化一个 Thread,然后,现在还有一种方式就是自定义一个 Thread 的子类,然后复写它的 run() 方法。

import threading
import time


class TestThread(threading.Thread):

    def __init__(self,name=None):
        threading.Thread.__init__(self,name=name)

    def run(self):
        for i in range(5):
            print(threading.current_thread().name + ' test ', i)
            time.sleep(1)


thread = TestThread(name='TestThread')
thread.start()


for i in range(5):
    print(threading.current_thread().name+' main ', i)
    print(thread.name+' is alive ', thread.isAlive())
    time.sleep(1)

上面的代码,我们自定义了 TestThread 这个类,然后继承了 threading.Thread。

只有在 run() 方法中处理逻辑。最终代码运行结果如下:

TestThread test  0
MainThread main  0
TestThread is alive  True
TestThread test  1
MainThread main  1
TestThread is alive  True
TestThread test  2
MainThread main  2
TestThread is alive  True
MainThread main  3
TestThread is alive  True
TestThread test  3
MainThread main  4
TestThread test  4
TestThread is alive  True

这与之前的效果并无差异,但我还是推荐用这种方法,毕竟面向对象编程嘛。

自此,Python 多线程编码技术就大致介绍完毕,大家可以进行实际代码编写了。

但是,多线程编程的难点在于多个线程之间共享数据的同步,这是非常容易出错的地方,我将分别编写相应的博文去介绍一些高级的技术点。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/briblue/article/details/85101144

智能推荐

博鳌亚洲论坛2023年会 国研政情·谋定论道-桑切斯:中欧合作行动-程序员宅基地

文章浏览阅读326次,点赞8次,收藏8次。博鳌亚洲论坛2023年会 国研政情·谋定论道-桑切斯:中欧合作行动

史上最全的Android面试题集锦,知乎上已获万赞_android 面试题库 知乎-程序员宅基地

文章浏览阅读99次。背景知乎客户端中有一个自己维护的 Hybrid 框架,在此基础上开发了一些 Hybrid 页面,当需要前端或者客户端开发接口的时候,就涉及到联调的问题。和一般的 前端 <=> 服务端,或者 客户端 <=> 服务端 类似,前端 <=> 客户端也会出现联调的各种问题,但是往往 Hybrid 开发相关的调试工具并不是那么完备。由于在 Hybrid 里面,前端和客户端联系是很紧密的,假如一个人既熟悉前端开发又熟悉客户端开发,调试是很容易的,但是现实一般是可能客户端开发并不是_android 面试题库 知乎

SpringBoot多模块项目打包_spingboot添加多个启动项目分别打包-程序员宅基地

文章浏览阅读269次。SpringBoot多模块项目打包_spingboot添加多个启动项目分别打包

【微电网优化】基于matlab YALMIP求解微网(光伏+风电+蓄电池+微电网+柴油机)优化调度问题【含Matlab源码 2266期】_matlab虚拟电厂调度优化-程序员宅基地

文章浏览阅读596次。YALMIP求解微网(光伏+风电+蓄电池+微电网+柴油机)优化调度问题完整代码,直接运行,适合小白!可提供运行操作视频!_matlab虚拟电厂调度优化

vue3 中使用el-dropdown时,slot报错`slot` attributes are deprecated_vue3 `slot` attributes are deprecated.-程序员宅基地

文章浏览阅读1k次,点赞3次,收藏4次。在VUE3中使用el-dropdown时,出现了下面的报错是因为vue 3.x 增加了v-slot的指令,去掉了原来的slot,slot-scope属性。el-dropdown-menu标签外面加上如下图:<el-dropdown> <span class="el-dropdown-link"> 下拉菜单<i class="el-icon-arrow-down el-icon--right"></i> </span>_vue3 `slot` attributes are deprecated.

ioc和di的关系-程序员宅基地

文章浏览阅读6.5k次,点赞6次,收藏9次。 刚进入spring的时候,看见这两个词一阵迷茫,控制反转不知道转的是啥,依赖注入也不知道注入的啥。有的人说这两个是一个,有的说di是ioc的实现,我就说说我的理解。 从getter和setter开始 从开始学习mvc设计模式,或者是jsp异或是框架,必然会有JavaBean或者其他类似的概念出现。getter和setter就是JavaBean中的..._ioc和di的关系

随便推点

7年点工,从为娶媳妇开始软件测试,到最后拍领导桌子甩头辞职···_软件测试纯点工-程序员宅基地

文章浏览阅读274次。7年的点工测试,薪资从1800到4500再到15K,为了让媳妇过得更好,决定来学自动化,实现买买买自由的初步小目标。_软件测试纯点工

记录MEMORY_MANAGEMENT蓝屏解决过程_ntoskrnl.exe memory_management-程序员宅基地

文章浏览阅读3.9w次,点赞10次,收藏45次。MEMORY_MANAGEMENT蓝屏解决过程问题描述win10,1909版本,一个月内蓝屏两至三次,都是在空闲时蓝屏,提示MEMORY_MANAGEMENT仅安装火绒,内存4+16(crucial,2666,后配的)解决过程首先我检查了下硬件,内存和硬盘没发现问题另排除病毒使用Debugging tools查看DMP文件,发现是由于ntoskrnl.exe导致命令提示符下执行chkdsk c: /f,重启修复c盘原因分析引用1:ntoskrnl.exe 是 Windows 操作系统的_ntoskrnl.exe memory_management

java开发工具包jdk包括哪些_java的工具包有多少兆代码-程序员宅基地

文章浏览阅读679次。害怕干不过SpringBoot?莫慌,我送你套神级pdf文档随着 Spring Boot 使用越来越广泛,Spring Boot 已经成为 Java 程序员面试的知识点,很多同学对 Spring Boot 理解不是那么深刻,经常就会被几个连环追问就给干趴下了!今天小编就给大家整理了全套SpringBoot“神级PDF文档”:面试篇:35常见知识点、21道必刷题、10个高频解析题知识脑图篇:综合解析SpringBoot知识大全预览进阶学习必备篇:《SpringBoot学习教程》、《深入_java的工具包有多少兆代码

java基础案例教程黑马程序员案例答案,真香_springboot黑马程序员课后答案-程序员宅基地

文章浏览阅读2.3k次。掌握核心知识1、90%几率面试被问,吃透原理,面试不慌(Spring原理)2、大厂必问Redis,赶紧码起来(Redis核心原理)3、MySQL从入门到实战都在这篇,面试笑谈优化当然核心知识不止这三点,这只是一部分吃透源码1、面试源码有捷径,Spring源码将无所畏惧(Spring源码解析)2、金三银四面试必问,MyBatis二级缓存揭秘3、面试加分课,Spring MVC源码将无所畏惧实战训练1、受用终生的Redis实战场景2、面试加分项(Spring Boot实战_springboot黑马程序员课后答案

如何完全删除解压版mysql_Mysql Window 解压版卸载-程序员宅基地

文章浏览阅读690次。windows如何彻底卸载mysql如何彻底删除mysql1.首先在windows服务中将mysql服务删掉,使用命令 sc delete mysql2.在控制面板中卸载掉mysql。3.清理mysql安装目录的ini文件。4.清理注册表:HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\Eventlog\Application\MySQL 目录删除..._windows怎么卸载解压版的mysql

linux安装mariadb数据库_linux a安装mariadb-程序员宅基地

文章浏览阅读1.5k次。1. 在linux系统安装mariadb数据库命令:yum install mariadb-server -- 安装mariadb数据库如果安装失败执行此命令:yum clean all 2. 确认下载:3. 安装成功提示:4. 数据库命令: 1. 启动命令 [root@localhost src]# systemctl start mariadb 2. 重启命令 [root@localhost src]# ..._linux a安装mariadb

推荐文章

热门文章

相关标签