【Python】多线程及threading模块介绍

article/2025/10/6 16:27:00

目录

1. 多线程简单介绍

2. threading模块介绍

2.1 threading模块常用方法

2.2 Thread类使用

2.2.1 使用构造函数传递可调用对象的方法创建线程

 2.2.2 继承threading.Thread类

3. 多线程程序中使用(共享)全局变量

4. 多线程共享全局变量遇到的问题

5. 线程同步

5.1  线程同步的解决机制:互斥锁

5.2 互斥锁可能造成的问题--死锁


活动地址:CSDN21天学习挑战赛

**

学习日记 Day8

**

1. 多线程简单介绍

多线程,或者说多任务,指的是操作系统同时运行多个任务。例如,听歌、洗衣服、看视频可以同时进行,这种就是多任务。

单核CPU执行多任务:操作系统轮流让各个任务交替执行,任务1执行t1时间,切换到任务2,任务2执行t2时间,再切换到任务3,执行t3时间...如此反复执行,表面上看,每个任务交替执行,但是由于CPU的执行速度很快,在人看来就好像所有任务同时执行。

这里需要注意并发和并行的概念:

  • 并发,指任务数多于COU核数,通过操作系统的各种任务调度算法,实现用多个任务一起执行(实际上有一些任务没有在执行,但因为切换任务的速度相当快,看上去一起执行)。
  • 并行,指的是任务数小于或等于CPU核数,任务是同时执行的。

2. threading模块介绍

python中提供了thread和threading模块对线程进行操作,其中thread模块是比较底层的模块,threading模块对thread做了一些包装,使用更方便。

2.1 threading模块常用方法

  • threading.active_count()                                返回当前处于active状态的Thread对象的个数
  • threading.current_thread()                             返回当前Thread对象
  • threading.get_ident()                                      返回当前线程的线程标识
  • threading.enumerate()                                    返回当前处于active状态的所有Thread对象列表
  • threading.main_thread()                                  返回主线程对象,启动python解释器的线程对象
  • threading.stack_size()                                      返回创建线程时使用的栈的大小,如果指定                                                                               size参数,则用来指定后续创建的线程使用的                                                                             栈大小,size必须是0(标识使用系统默认值)                                                                            或大于32K的正整数

注:线程标识是一个非负整数,并无特殊含义,只是用来标识线程,该整数可能会被循环利用,python3.3及以后版本支持该方法。


threading模块提供了Thread、Lock、RLock、Condition、Event、Timer和Semaphore等类支持多线程。


2.2 Thread类使用

Thread是threading提供的最重要也是最基本的类,可以通过该类创建线程并控制线程的运行。

使用Thread创建线程,有两种方式:

  1. 为构造函数传递一个可调用对象
  2. 继承Thread类并在子类中重写__init__()和run()

threading.Thread类常用的方法和属性如下

  • 常用方法
方法名说明
start()启动线程
run()线程代码,用来实现线程的功能和业务逻辑,可以在子类中重写该方法自定义线程的行为
init(self,group=None,target=None,name=None,args=(),kwargs=None,daemon=None)构造函数
is_alive()判断线程是否存货
getName()返回线程名
setName()设置线程名
isDaemon()判断线程是否为守护线程
setDaemon()设置线程是否为守护线程
  • 常用属性

属性名说明
name读取或设置线程的名字
ident线程标识,用非0数字或None(线程未被启动)
daemon标识线程是否为守护线程,默认为False
join(timeout=None)

当timeout=None时,会等待至线程结束;

否则,会等待至timeout时间结束,单位为秒

2.2.1 使用构造函数传递可调用对象的方法创建线程

threading.Thread类的构造函数声明如下:

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

参数 group 通常默认,作为扩展ThreadGroup类保留。
参数 target 用于run()方法调用的可调用对象,默认为None,标识run()不调用任何对象。
参数 name 线程名称,默认是Thread-N格式狗策划给你的唯一名称,N是十进制数。
参数 args  用于调用目标函数的参数元组,默认是()。
参数 kwargs  用于调用目标函数的关键字参数字典,默认为{}。
参数 daemon 设置线程是否为守护模式,默认为None。

示例1 :使用threading模块创建单线程

import threading
import timedef sing():print("正在唱歌...")time.sleep(1)if __name__ == "__main__":print("======================调用函数======================")print("开始时间:%s"%time.ctime())for i in range(10):sing()print("结束时间:%s"%time.ctime())print("==========threading.Thread.start()开启单线程==========")print("开始时间:%s"%time.ctime())for i in range(10):t = threading.Thread(target=sing)t.start()print("结束时间:%s"%time.ctime())

显示结果,可以看出来使用threading.Thread启动线程时,使用的时间要短很多。

 当调用start()时,才开始真正的创建线程,并开始执行。

示例2:使用threading.Thread创建多线程,并查看当前处于active状态的线程的数量。

import threading
from time import ctime,sleepdef sing():for i in range(3):print("singing...%d"%i)sleep(1)def dance():for i in range(3):print("dancing...%d"%i)sleep(1)if __name__ == "__main__":print("---start---:%s"%ctime())t1 = threading.Thread(target=sing)t2 = threading.Thread(target=dance)t1.start()t2.start()while True:length = len(threading.enumerate())print("current thread has %d"%length)if length <= 1:breaksleep(0.5)print("---end---:%s"%ctime())

显示结果:

 2.2.2 继承threading.Thread类

在使用threading模块时,也可以定义一个新的类class,继承threading.Thread,然后重写run()。

threading.Thread类中的run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。创建自己的线程实例后,通过Thread类的start方法,可以启动该线程。

示例1:创建一个继承自threading.Thread类的子类

import threading
import timeclass MyThread(threading.Thread):def run(self):for i in range(3):time.sleep(1)msg = "I'm " + self.name + " @ " + str(i)  # name保存当前线程的名字print(msg)if __name__ == "__main__":t = MyThread()t.start()

显示结果:

 示例2:观察线程执行的顺序

import threading
import timeclass MyThread(threading.Thread):def run(self):for i in range(3):time.sleep(1)msg = "I'm " + self.name + " @ " + str(i)  # name保存当前线程的名字print(msg)def test():for i in range(5):t = MyThread()t.start()if __name__ == "__main__":test()

显示结果:

 每次运行时显示的执行顺序是不确定的。当执行到sleep()的时候,当前线程被阻塞,sleep()结束后当前线程进入就绪状态,等待调度。

线程调度会自行选择一个线程执行。

如上代码只能保证每个线程都能运行完整个run函数,但线程的启动顺序、run函数中每次循环的执行顺序都不能确定。

综上总结:

  1. 每个线程默认有一个名字,没有指定,python会自动为线程指定一个名字/
  2. 当线程的run()方法结束时该线程完成;
  3. 无法控制线程调度程序,可以通过别的方式影响线程调度方式。

3. 多线程程序中使用(共享)全局变量

在一个进程中的所有线程共享全局变量,很方便在多个线程间共享数据;然而,线程对全局变量随意更改可能造成多线程之间对全局变量的混乱。

示例1:多线程共享全局变量

from threading import Thread
import timeg_num=100def work1():global g_numfor i in range(3):g_num += 1print("----in work1,g_num = %d"%g_num)def work2():global g_numprint("----in work2,g_num = %d"%g_num)print("----线程创建之前g_num = %d"%g_num)t1 = Thread(target=work1)
t1.start()time.sleep(1)t2 = Thread(target=work2)
t2.start()

显示结果,可以看到在work1()中改变了全局变量g_num的值,在work2中g_num也显示了变化。

示例2:列表当作实参传递到线程中

from threading import Thread
import threading
import timedef work1(nums):nums.append(44)print("----in work1----",nums)def work2(nums):time.sleep(1)print("----in work2----",nums)g_nums = [11,22,33]t1 = threading.Thread(target=work1,args=(g_nums,))
t1.start()t2 = threading.Thread(target=work2,args=(g_nums,))
t2.start()

显示结果:

----in work1---- [11, 22, 33, 44]
----in work2---- [11, 22, 33, 44]

4. 多线程共享全局变量遇到的问题

多线程开发可能遇到的问题:假设线程t1和t2都要对全局变量g_num(默认是0)进行加1运算,t1和t2都各对g_num加10次,g_num的最终结果应该为20。

现在测试下

import threading
import timeg_num = 0def work1(num):global g_numfor i in range(num):g_num += 1print("----in work1,g_num is %d---"%g_num)def work2(num):global g_numfor i in range(num):g_num += 1print("----in work2,g_num is %d---"%g_num)print("---创建线程之前g_num is %d"%g_num)t1 = threading.Thread(target=work1,args=(100000,))
t1.start()t2 = threading.Thread(target=work2,args=(100000,))
t2.start()while len(threading.enumerate()) != 1:time.sleep(1)print("---两个线程对同一个全局变量操作之后的最终结果是:%d"%g_num)

显示结果,t1和t2都对g_num进行加1运算,各进行100000次,那么期待的最终结果应该是200000,然而实际测试的结果不是200000,很显然是有问题的。

---创建线程之前g_num is 0
----in work1,g_num is 100000---
----in work2,g_num is 155034---
---两个线程对同一个全局变量操作之后的最终结果是:155034

多线程同时操作时,会出现资源竞争问题,从而导致数据结果不正确。可能会出现如下情况:

  1. g_num=0时,t1取得g_num=0,此时系统把t1调度为sleeping状态,把t2转换为running状态,t2也获得g_num=0
  2. 然后t2对得到的值进行加1并赋值给g_num,使得g_num=1
  3. 然后系统又把t2调度为sleeping,把t1转为running,此时t1得到的g_num=0,加1后g_num=1;
  4. 这样,t1和t2各对g_num加1,期望的结果是g_num=2,实际上此时g_num=1

5. 线程同步

同步标识按预定的先后次序进行运行。如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。

使用Thread对象的Lock和RLock可以简单实现线程同步。这两个对象都有acquire方法和release方法,对于需要每次只允许一个线程操作的数据,可以将其操作放到acquire()和release()之间。

对于上述资源竞争导致的数据结果不正确的问题,可以通过线程同步解决,如下:

  1. 系统调用t1,获取到g_num=0,此时上一把锁,不允许其他线程操作g_num;
  2. t1对g_num的值进行+1
  3. t1解锁,此时g_num=1,其他线程可以使用g_num,此时g_num=1
  4. 同理,其他线程对g_num进行修改时,要先上所,处理完后再解锁,在上锁过程中不允许其他线程访问,就保证了数据的正确性

5.1  线程同步的解决机制:互斥锁

多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制。线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁

互斥锁为资源引入一个状态:锁定/非锁定。

某个线程要更改共享数据时,先将其锁定,此时资源的状态为【锁定】,其它线程不能更改;直到该线程释放资源,将资源的状态编程【非锁定】,其它线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性

threading模块定义了Lock类,处理锁/解锁很方便。

# 创建锁
mutex = threading.Lok()# 加锁
mutex.acquire()#解锁
mutex.release()

需要注意的是:

  • 如果这个锁之前是没有上锁,acquire()不会堵塞
  • 如果在调用acquire上锁前,它已经被其它线程上了锁,那么此时acquire会堵塞,直到这个锁被解锁为止。

使用示例:

import threading
import timeg_num = 0def work1(num):global g_numfor i in range(num):mutex.acquire()  # 上锁g_num += 1mutex.release()  # 解锁print("----in work1,g_num is %d---"%g_num)def work2(num):global g_numfor i in range(num):mutex.acquire()g_num += 1mutex.release()print("----in work2,g_num is %d---"%g_num)print("---创建线程之前g_num is %d"%g_num)# 创建互斥锁,默认是未上锁状态
mutex = threading.Lock()t1 = threading.Thread(target=work1,args=(100000,))
t1.start()t2 = threading.Thread(target=work2,args=(100000,))
t2.start()while len(threading.enumerate()) != 1:time.sleep(1)print("---两个线程对同一个全局变量操作之后的最终结果是:%d"%g_num)

显示结果,发现加入互斥锁后,其结果与预期相符。

---创建线程之前g_num is 0
----in work1,g_num is 183100---
----in work2,g_num is 200000---
---两个线程对同一个全局变量操作之后的最终结果是:200000

上锁解锁过程:

  1. 当一个线程嗲用锁的acquire()方法获得锁时,锁就进入【locked】状态;
  2. 每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为【blocked】状态,称为阻塞。直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入【unlock】状态;
  3. 线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入【running】状态。

使用锁可以确保某段关键代码只能由一个线程从头到尾完整地执行;但是使用锁终止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就下降了,并且由于可以存在多个所,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁

5.2 互斥锁可能造成的问题--死锁

在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。

死锁一旦发生,就会造成应用停止响应。

如下演示了死锁:

import threading
import timeclass MyThread1(threading.Thread):def run(self):# 对mutex1上锁mutex1.acquire()# mutex1上锁后延时,等待另一个线程对mutex2上锁print(self.name + "---do1---lock 1---")time.sleep(1)# 对mutex2上锁,此时会阻塞,因为mutex2已经被另外的线程上锁了mutex2.acquire()print(self.name + "---do1---lock 2---")mutex2.release()  # 解锁mutex2# 解锁mutex1mutex1.release()class MyThread2(threading.Thread):def run(self):# 对mutex2上锁mutex2.acquire()# mutex2上锁后延时,等待另一个线程对mutex1上锁print(self.name + "---do2---lock 2---")time.sleep(1)# 对mutex1上锁,此时会阻塞,因为mutex1已经被另外的线程上锁了mutex1.acquire()print(self.name + "---do2---lock 1---")mutex1.release()  # 解锁mutex1# 解锁mutex2mutex2.release()mutex1 = threading.Lock()
mutex2 = threading.Lock()if __name__ == "__main__":t1 = MyThread1()t2 = MyThread2()t1.start()t2.start()

显示结果:

 

避免死锁,程序设计时要尽量避免可能出现死锁的情况;添加超时时间等待。

全文参考:100天精通Python(进阶篇)——第37天:多线程(threading模块)_无 羡ღ的博客-CSDN博客https://blog.csdn.net/yuan2019035055/article/details/122656131


http://chatgpt.dhexx.cn/article/JvUaZcxp.shtml

相关文章

threading库:Python线程的基础知识

目录 前言Thread对象区分线程守护线程自定义线程定时器线程线程间传送信号 前言 前面的subprocess库主要讲解的是进程知识与进程间的交互。而进程有可以拥有多个线程&#xff0c;所以threading库提供了管理多个线程执行的API&#xff0c;允许程序在同一个进程空间并发地运行多…

Python Threading 线程模块用法

一、什么是 Threading Threading用于提供线程相关的操作&#xff0c;线程是应用程序中工作的最小单元。python当前版本的多线程库没有实现优先级、线程组&#xff0c;线程也不能被停止、暂停、恢复、中断。 1.1、线程池图解 二、创建线程 导入模块threading&#xff0c;通过…

python中threading模块_python中threading模块详解

python中threading模块详解,threading提供了一个比thread模块更高层的API来提供线程的并发性。这些线程并发运行并共享内存。 下面来看threading模块的具体用法: 一、Thread的使用 目标函数可以实例化一个Thread对象,每个Thread对象代表着一个线程,可以通过start()方法,开…

python中的threading_python中threading的用法

threading提供了一个比thread模块更高层的API来提供线程的并发性。这些线程并发运行并共享内存。 下面来看threading模块的具体用法&#xff1a; 一、Thread的使用 目标函数可以实例化一个Thread对象&#xff0c;每个Thread对象代表着一个线程&#xff0c;可以通过start()方法…

Python--threading多线程总结

转载自&#xff1a;https://www.cnblogs.com/tkqasn/p/5700281.html threading用于提供线程相关的操作&#xff0c;线程是应用程序中工作的最小单元。python当前版本的多线程库没有实现优先级、线程组&#xff0c;线程也不能被停止、暂停、恢复、中断。 threading模块提供的类&…

Python 批量创建线程及threading.Thread类的常用函数及方法

在《【Python】线程的创建、执行、互斥、同步、销毁》&#xff08;点击打开链接&#xff09;中介绍了Python中线程的使用&#xff0c;但是里面线程的创建&#xff0c;使用了很原始的方式&#xff0c;一行代码创建一条。其实&#xff0c;Python里是可以批量创建线程的。利用Pyth…

功能强大的python包(十一):threading (多线程)

1.threading简介 threading库是python的线程模型&#xff0c;利用threading库我们可以轻松实现多线程任务。 2.进程与线程简介 通过上图&#xff0c;我们可以直观的总结出进程、线程及其之间的关系与特点&#xff1a; 进程是资源分配的最小单元&#xff0c;一个程序至少包含…

一文搞明白Python多线程编程:threading库

目录 前言一、基础知识1、并行和并发&#xff08;1&#xff09;定义&#xff08;2&#xff09;联系 2、进程和线程&#xff08;1&#xff09;定义&#xff08;2&#xff09;联系 3、全局解释器锁GIL 二、threading库1、线程的使用&#xff08;1&#xff09;普通创建方式&#x…

分块矩阵求逆(推导)

关于分块矩阵求逆&#xff0c;其中对角矩阵比较简单&#xff0c;我看很多人都写了&#xff0c;并且很详细。 但关于AUVD的分块矩阵我没看到太让我明白的&#xff0c;可能我get不到点&#xff0c;数学基础差&#xff0c;我就自己写了详细的步骤。 我写的这个条件是A可逆&#…

伴随矩阵求逆矩阵

在之前的文章《线性代数之矩阵》中已经介绍了一些关于矩阵的基本概念&#xff0c;本篇文章主要就求解逆矩阵进行进一步总结。 余子式(Minor) 我们先看例子来直观的理解什么是余子式(Minor&#xff0c;后边将都用英文Minor&#xff0c;中文的翻译较乱&#xff09;。 minor exa…

矩阵的逆

矩阵的逆 原理&#xff1a; 连接&#xff1a; https://baike.baidu.com/item/%E9%80%86%E7%9F%A9%E9%98%B5 代码&#xff1a; QT c版&#xff1a; //求逆矩阵 QVector<QVector<double>> Matrix_inverse(QVector<QVector<double>> &A) {int …

矩阵求逆

矩阵求逆 初等变换求逆参考文章 初等变换求逆 1&#xff09;初等变换求逆矩阵时不能同时使用初等列变换和初等行变换&#xff0c;使用初等列变换或者初等行变换来求逆矩阵都是可以的&#xff0c;但是不能二者同时使用&#xff0c;只能用一种方法来得到逆矩阵。 2&#xff09;初…

卡西欧计算器矩阵求逆

记录一下&#xff0c;防止忘记 1 按开机键 打开计算器 2 按 菜单设置键 进去计算模式选择模块 3 按“ 4 ”选择矩阵运算 4 有四个矩阵可以编辑 选择按 4 编辑矩阵D 5 输入矩阵的行数 我按了3 6 输入矩阵的列数 我按了 3 7 建了一个3*3空矩阵 输入第一个数 12 然后按“”键完…

如何用计算机求矩阵的逆矩阵,逆矩阵的求法

逆矩阵是数学知识的一种&#xff0c;很多学习数学的同学们应该很了解吧。逆矩阵计算器是一款可以对矩阵的逆进行计算的免费程序,本程序引入了分数算法,可以对分数元素计算并得出分数结果。那么这款软件怎么样呢&#xff1f;接下来&#xff0c;介绍一下。 逆矩阵的求法 A^(-1)(1…

python求逆矩阵的方法,Python 如何求矩阵的逆

我就废话不多说了,大家还是直接看代码吧~ import numpy as np kernel = np.array([1, 1, 1, 2]).reshape((2, 2)) print(kernel) print(np.linalg.inv(kernel)) 注意,Singular matrix奇异矩阵不可求逆 补充:python+numpy中矩阵的逆和伪逆的区别 定义: 对于矩阵A,如果存在一…

Maple矩阵求逆

如何使用Maple进行矩阵求逆 调用包 with(LineAlgebra);输入我想要的矩阵&#xff1a; R : Matrix([[cos(a), sin(a), 0], [-sin(a), cos(a), 0], [0, 0, 1]]);然后我使用了Inverse命令 simplify(Inverse(R));结果并没有生成矩阵的逆。 查了一下说明手册加上mod好像也不行&a…

线性代数 --- 矩阵求逆的4种方法

线性代数 --- 矩阵求逆的4种方法 写在最前面&#xff1a;在大多数情况下&#xff0c;我们学习线性代数的目的是为了求解线性方程组Axb&#xff0c;而不是为了求A的逆。 单就解方程而言&#xff0c;LU分解是最实用的算法。只需按照ALU——>Axb,LUxb——>Lyb(正向回代求得y…

求解逆矩阵的常用三种方法

1.待定系数法 矩阵A 1, 2 -1,-3 假设所求的逆矩阵为 a,b c,d 则 从而可以得出方程组 a 2c 1 b 2d 0 -a - 3c 0 -b - 3d 1 解得 a3; b2; c -1; d -1 2.方程组求逆矩阵 伴随矩阵是矩阵元素所对应的代数余子式&#xff0c;所构成的矩阵&#xff0c;转置后得到的新矩阵。 …

矩阵求逆四种方法

注&#xff1a; 用A、B表示某矩阵, E表示单位矩阵 用Aˊ表示A逆 用|A|表示A的行列式 &#xff3b;A|E&#xff3d;表示拼接矩阵 一、公式法 先求A行列式结果&#xff0c;再求A伴随矩阵&#xff0c;最后再求A逆矩阵 |A| &#xff01; 0 则 AˊA*/|A| 注&#xff1a;图片中det…

浏览器渲染页面和加载页面机制

为什么有些网站打开的时候会加载会很慢&#xff0c;而且是整个页面同时显示的&#xff0c;而有些网站是从顶到下逐步显示出来的&#xff1f;弄懂这些对前端性能优化有很大帮助。要搞懂这个可以先从下面这个常规流程开始&#xff1a; 常规流程 1. 浏览器下载的顺序是从上到下&am…