Python中,线程threading详解

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

Python中最常用的同步有:锁/互斥,以及信号量。其中锁是最简单最低级的机制,信号量用于多线程竞争有限资源的情况。但是锁被释放,线程不一定被释放。
threading.Lock同步锁(原语锁)
通常使用获得锁(加锁)和释放锁(解锁)函数来控制锁的两种状态,在Python中,只需要在公共操作中加上加锁和解锁的操作即可。
如“加锁”和“解锁“操作
通过lock.acquire()获得锁后,线程将一直执行,直到该线程lock.release()的锁被释放,线程才有可能被释放(注意:锁被释放,线程不一定被释放)。

# 创建一个锁对象
lock = threading.Lock()def func():# 全局变量global num# 获得锁、加锁# lock.acquire()num1 = numtime.sleep(0.1)num = num1 - 1# 释放锁、解锁# lock.release()time.sleep(2)num = 100l = []# 开启100个线程
for i in range(100):t = threading.Thread(target=func, args=())t.start()l.append(t)# 等待线程运行结束
for i in l:i.join()print(f'num={num}')

在上面的示例中,先将lock.acquire()和lock.release()代码注释掉,表示不使用锁,取消lock.acquire()和lock.release()代码注释表示使用加锁和解锁。在代码中增加了time.sleep(0.1),表示在不使用锁时,线程在这里将被释放出来,让给下一个线程执行,而num的值还未被修改,所以后面线程的num1取值都为100。

代码运行结果为:
加锁和解锁时:num=0
不加锁和解锁时:num = 99
Python中,Lock锁与GIL锁的区别
Lock锁的目的是为了保护共享数据,同一时刻只能有一个线程修改共享变量数据,而保护不同的数据需要使用不同的锁。而GIL(全局解释器锁)用于限制一个进程中同一时刻只有一个线程被CPU调度。GIL的级别比Lock高,是解释器级别。
死锁
多线程中最怕遇到的就是死锁,当两个或两个以上的线程在执行时,因争夺资源被相互锁住而互相等待。
示例:互锁造成的死锁

# 生成锁对象
lock1 = threading.Lock()
lock2 = threading.Lock()class MyThread(threading.Thread):def __init__(self):threading.Thread.__init__(self)def run(self) -> None:self.funA()self.funB()def funA(self):lock1.acquire()print(f"A_1加锁", end='\t')lock2.acquire()print(f'A_2加锁', end='\t')time.sleep(0.2)lock2.release()print(f'A_2释放', end='\t')lock1.release()print(f'A_1释放')def funB(self):lock2.acquire()print(f'B_1加锁', end='\t')lock1.acquire()print(f'B_2加锁', end='\t')time.sleep(0.1)lock1.release()print(f'B_2释放', end='\t')lock2.release()print(f'B_1释放')if __name__ == '__main__':t1 = MyThread()t2 = MyThread()t1.start()t2.start()

运行死锁的结果:
在这里插入图片描述

未死锁的结果:
在这里插入图片描述

如果两个锁同时被多个线程运行,可能会出现死锁的情况,也可能未出现死锁情况,这是潜在的BUG,也是较难找的BUG。
threading.RLock重入锁(递归锁)
为了支持同一线程多次请求同一资源,python提供了可重入锁(RLock),RLock内部维护着一个锁(Lock)和一个计数器(counter)变量,计数器(counter)记录了acquire的次数,从而使得资源可以被多次acquire,直到一个线程所有acquire都被release,计数器counter为0,其它线程才能获得资源。
例如:


```python
Rlock = threading.RLock()class MyThread(threading.Thread):def __init__(self):threading.Thread.__init__(self)def run(self) -> None:self.funA()self.funB()def funA(self):Rlock.acquire()print(f"A_1加锁", end='\t')Rlock.acquire()print(f'A_2加锁', end='\t')time.sleep(0.2)Rlock.release()print(f'A_2释放', end='\t')Rlock.release()print(f'A_1释放')def funB(self):Rlock.acquire()print(f'B_1加锁', end='\t')Rlock.acquire()print(f'B_2加锁', end='\t')time.sleep(0.1)Rlock.release()print(f'B_2释放', end='\t')Rlock.release()print(f'B_1释放')if __name__ == '__main__':t1 = MyThread()t2 = MyThread()t1.start()t2.start()

执行结果
在这里插入图片描述

当程序运行到time.sleep(0.2)时,也不会切换线程。使用重入锁时,计数器(counter)不为0(所有的acquire都还没有被释放掉),即使遇到长时间的IO操作也不会切换线程。
threading.Semaphore信号量
信号量是一个内部数据,它有一个内置计数器的计数器,它标明当前的共享资源有多少线程可以读取。
如,定义一个只能同时执行4个线程的信号量

# 创建信号量对象,信号量设置为4,需要有4个线程才启动,4个线程并发
semaphore = threading.Semaphore(4)

当线程需要读取关联信号的共享资源时,需要调用semaphore.acquire(),这时信号量的计数器为-1。

semaphore.acquire() # 获取信号量 -1

当线程不需要共享资源时,需要释放信号semaphore.release(),这时信号量的计数器(counter)会加1,在信号量等待队列中,排在最前面的线程会拿到共享资源的权限。
如,信号量为4的线程并行运行

# 创建信号量对象,信号量设置为4,需要有4个线程才启动
semaphore = threading.Semaphore(4)
def funcSemaphore():if semaphore.acquire(): # 获取信号量 -1print(f'{threading.currentThread().getName()}获得信号量')time.sleep(random.randint(1, 10))semaphore.release() # 释放信号量 +1
if __name__ == '__main__':for i in range(1, 8):t1 = threading.Thread(target=funcSemaphore)t1.start()

运行结果
在这里插入图片描述

开始时只有4个线程获得资源权限,后面当释放多少资源时,就有多少线程能够获得资源权限。
例如,运用信号量进行线程同步

# 同步两个不同的线程,信号量被初始化为0
semaphore0 = threading.Semaphore(0)
def consumer():print("---等待producer运行---")# 获取资源,信号量为0时被挂起,等待信号量释放semaphore0.acquire()print("---consumer 结束---编号:%s" % item)def producer():global itemtime.sleep(3)# 生成随机变量item = random.randint(0, 100)print("procuder运行编号:%s" %item)semaphore0.release()
if __name__ == '__main__':for i in range(0, 8):t1 = threading.Thread(target=producer)t2 = threading.Thread(target=consumer)t1.start()t2.start()t1.join()t2.join()print("程序终止!")

运行结果
在这里插入图片描述

信号量被初始化为0,目的是同步两个或多个线程。线程必须并行运行,所以需要信号量同步。这种运用场景有时会用到,理解起来较难。
信号量在多线程中运用较广,但它也有可能造成死锁的情况。如,有一个线程A1,先等待信号量S1,在等待信号量S2,而线程B1,先等待信号量S2,在等待信号量S1,这样就会发生死锁,导致A1等待S2,B1等待S1。
threading.Condition()条件变量
Condition条件变量通常与一个锁相关联。当需要在多个Condition条件中共享一个锁时,可以传递一个Lock/RLock实例给构造方法,否则它将产生一个RLock实例。
条件变量锁实例定义

# 条件变量对象
conn = threading.Condition()

在Condition下的方法:
acquire() 获得锁(线程锁)。
release() 释放锁。
wait(timeout=None) 挂起线程timeout秒(为None时,时间无限),直到收到notify通知或者超时才会被唤醒继续运行,必须在Lock锁下运行。
notify(n=1) 通知挂起的线程开始运行,默认通知正在等待该Condition的线程,可同时唤醒n个,必须在Lock锁下运行。
notifyAll() 通知所有被挂起的线程开始运行,必须在Lock锁下运行。

示例,生产者与消费者,线程produce生产产品并通知消费者(consume线程)进行购买,线程consume消费完后通知生产者进行生产。

import threading
import time# 商品
product = None
# 条件变量对象
conn = threading.Condition()# 生产方法
def produce():# 全局变量产品global productif conn.acquire():while True:print("---执行produce---")if product is None:product = '电脑'print(f"---生产产品:{product}---")# 通知消费者,商品已经生产conn.notify() # 唤醒消费线程,通知挂起线程继续运行,默认通知正在等待该condition的线程,可同时唤醒n个,且必须在获得Lock下运行# 等待通知, 挂起线程毫秒(为None时,时间无限),直到收到notify或者超时才会继续运行conn.wait()time.sleep(2)# 消费方法
def consume():global productif conn.acquire():while True:print("***执行consume***")if product is not None:print(f"***卖出产品:{product}***")product = None# 通知生产者,产品已卖出conn.notify()# 等待通知conn.wait()time.sleep(2)
if __name__ == "__main__":t1 = threading.Thread(target=consume)t1.start()t2 = threading.Thread(target=produce)t2.start()

运行结果
在这里插入图片描述

示例2,生产一定产品数量后进行消费

# 生产数量达到一定条件后被消费
num = 0
condition = threading.Condition()class Producer(threading.Thread):'''生产者,生产商品,10个后等待消费'''def run(self) -> None:global num# 获得锁condition.acquire()while True:num += 1print("生产了1个,现在有{0}个".format(num))time.sleep(1)if num >= 6:print("已到达到6个,停止生产")# 唤醒消费者线程condition.notify()# 等待释放锁 或者 被唤醒获取锁condition.wait()class Consumer(threading.Thread):'''消费者抢购商品,每人初始20元,商品单价2元'''def __init__(self, *args, **kwargs):super(Consumer, self).__init__(None, None)self.money = 12def run(self) -> None:global numwhile self.money > 0:condition.acquire()if num <= 0:print("商品已卖完,{0}通知生产者".format(threading.current_thread().name))condition.notify()condition.wait()self.money -= 2num -= 1print("{0}消费了1个,剩余{1}个".format(threading.current_thread().name, num))condition.release()time.sleep(1)print("{0}钱已花完,停止消费".format(threading.current_thread().name))if __name__ == "__main__":p = Producer(daemon=True)c1 = Consumer(name="Consumer--1")c2 = Consumer(name="Consumer--2")p.start()c1.start()c2.start()

运行结果
在这里插入图片描述

上述两个示例大致差不多,只是实现方式不同。
release()和wait()的区别
相同点:release()和wait()都有释放锁的作用。
**不同点:**在wait()后,线程会挂起等待,若要继续执行,就需要接收到notify()或者notifyAll()来唤醒线程;而release()后,该线程还能继续执行。
threading.Event() 事件锁对象
Event事件锁对象用于线程之间的通信,即程序中的其中一个线程通过判断某个线程的状态来确定自己的下一步操作。Event对象有状态值,其默认为False,即遇到Event对象就阻塞线程运行。
Event中的对象方法:
wait(timeout=None) 挂起线程timeout秒(None为时间无限),直到超时或者收到event()信号开关为True时,才唤醒程序。
set() event状态设置为True。
clear() event状态设置为False。
isSet() 返回event对象的状态值。
Event事件锁对象实例定义

# Event对象方法
event = threading.Event()

例如:

# Event对象方法
event = threading.Event()def func():print('等待响应...')event.wait()print('连接到服务')def connect():print('成功启动服务')event.set()
if __name__ == "__main__":t1 = threading.Thread(target=func)t2 = threading.Thread(target=connect)t1.start()t2.start()

运行结果
在这里插入图片描述

从运行结果可以看出,func函数需要等待connect函数运行event.set()之后才继续执行操作。


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

相关文章

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

​目录 1. 多线程简单介绍 2. threading模块介绍 2.1 threading模块常用方法 2.2 Thread类使用 2.2.1 使用构造函数传递可调用对象的方法创建线程 2.2.2 继承threading.Thread类 3. 多线程程序中使用(共享)全局变量 4. 多线程共享全局变量遇到的问题 5. 线程同步 5.1 …

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…