最新要闻
- “格物致知 叩问苍穹”2023年中国航天踏上新征程
- 世界热头条丨海南房子合适买吗?《滨江商业广场》优势不足全面解读~
- 世界今头条!小米13 Ultra被抢购一空!雷军:备货非常困难
- 全球热点!发改委:下大力气稳定汽车消费 鼓励车企开发乡村“专车”
- 32.88万起售 全新林肯航海家上市:环绕大屏加持如开星际飞船
- 环球实时:暗光长焦加持!OPPO Reno10 Pro+来了:堪称“小Find X6”
- 每日快看:误入“小人国”!华为P60 Pro长焦微距效果惊艳
- 活性水是什么意思?活性水和直饮水的区别是什么?
- 红眼病是怎么引起的?红眼病的症状有哪些?
- 天天观察:香港金管局再承接69.16亿港元沽盘?
- 打不过啊!本赛季老鹰对绿军0胜5负 场均净负13.2分
- 快看点丨极端高温出现 印度一颁奖礼热死11人:每人获赔4.1万
- 热门:开卷!微软拟自研AI芯片“戴安娜”:降低机器学习成本
- 黑入iPhone仅需一条iMessage消息!多起NSO“零点击”攻击曝光
- 米粉首发入手小米13 Ultra:实在太香了 小米11 Pro终于退休了
- 一体成型无拼接:康巴赫整竹双面砧板69元新低(90元券)
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
python 多线程详解
概念
线程是处理器调度和分配的基本单位,进程则作为资源拥有的基本单位。每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成。线程是进程内部的一个执行单元。每一个进程至少有一个主执行线程,它无需由用户去主动创建,是由系统自动创建的。 用户根据需要在应用程序中创建其它线程,多个线程并发地运行于同一个进程中。
(资料图)
创建线程的方式-threading
方法1
在实例化一个线程对象时,将要执行的任务函数以参数的形式传入threading
# -*- coding: utf-8 -*-import timeimport threadingimport datetimedef printNumber(n: int) -> None: while True: times = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") print(f"{times}-{n}") time.sleep(n)for i in range(1, 3): t = threading.Thread(target=printNumber, args=(i,)) t.start()# 输出2022-12-16 11:04:40-12022-12-16 11:04:40-22022-12-16 11:04:41-12022-12-16 11:04:42-22022-12-16 11:04:42-12022-12-16 11:04:43-12022-12-16 11:04:44-22022-12-16 11:04:44-12022-12-16 11:04:45-12022-12-16 11:04:46-22022-12-16 11:04:46-12022-12-16 11:04:47-1....Process finished with exit code -1
创建两个线程,一个线程每隔一秒打印一个“1”,另一个线程每隔2秒打印一个“2”Thread 类提供了如下的init() 构造器,可以用来创建线程:
__init__(self, group=None, target=None, name=None, args=(), kwargs=None, *,daemon=None)
Python此构造方法中,以上所有参数都是可选参数,即可以使用,也可以忽略。其中各个参数的含义如下:
group:指定所创建的线程隶属于哪个线程组(此参数尚未实现,无需调用);
target:指定所创建的线程要调度的目标方法(最常用);
args:以元组的方式,为 target 指定的方法传递参数;
kwargs:以字典的方式,为 target 指定的方法传递参数;
daemon:指定所创建的线程是否为后代线程。
这些参数,初学者只需记住 target、args、kwargs 这 3 个参数的功能即可。但是线程需要手动启动才能运行,threading 模块提供了 start() 方法用来启动线程。因此在上面程序的基础上,添加如下语句:t.start()
方法2
通过继承 Thread 类,我们可以自定义一个线程类,从而实例化该类对象,获得子线程。
需要注意的是,在创建 Thread 类的子类时,必须重写从父类继承得到的 run() 方法。因为该方法即为要创建的子线程执行的方法,其功能如同第一种创建方法中的 printNumber() 自定义函数。
# -*- coding: utf-8 -*-import datetimeimport timeimport threadingclass MyThread(threading.Thread): def __init__(self, n): self.n = n # 注意:一定要调用父类的初始化函数 super().__init__() def run(self) -> None: while True: times = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") print(f"{times}-{self.n}") time.sleep(self.n)for i in range(1, 3): t = MyThread(i) t.start()# 输出2022-12-16 12:43:24-12022-12-16 12:43:24-22022-12-16 12:43:25-12022-12-16 12:43:26-22022-12-16 12:43:26-12022-12-16 12:43:27-12022-12-16 12:43:28-2...
主线程和子线程
# -*- coding: utf-8 -*-import datetimeimport timeimport threadingclass MyThread(threading.Thread): def __init__(self, n): self.n = n # 注意:一定要调用父类的初始化函数,否则无法创建线程 super().__init__() def run(self) -> None: while True: _count = threading.active_count() threading_name = threading.current_thread().getName() times = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") print(f"{times}-{self.n}-"当前活跃的线程个数:{_count}"-"当前线程的名称是":{threading_name}") time.sleep(self.n)for i in range(1, 3): t = MyThread(i) t.start() print(threading.current_thread().getName())# 输出2022-12-16 13:18:00-1-"当前活跃的线程个数:2"-"当前线程的名称是":Thread-1MainThread2022-12-16 13:18:00-2-"当前活跃的线程个数:3"-"当前线程的名称是":Thread-2MainThread2022-12-16 13:18:01-1-"当前活跃的线程个数:3"-"当前线程的名称是":Thread-12022-12-16 13:18:02-2-"当前活跃的线程个数:3"-"当前线程的名称是":Thread-22022-12-16 13:18:02-1-"当前活跃的线程个数:3"-"当前线程的名称是":Thread-12022-12-16 13:18:03-1-"当前活跃的线程个数:3"-"当前线程的名称是":Thread-12022-12-16 13:18:04-2-"当前活跃的线程个数:3"-"当前线程的名称是":Thread-22022-12-16 13:18:04-1-"当前活跃的线程个数:3"-"当前线程的名称是":Thread-1...
注意:第一次t.start()后,当前存在两个线程(主线程+子线程),第二次t.start()的时候又创建了一个子线程所以当前存在三个线程
如果程序中不显式创建任何线程,则所有程序的执行,都将由主线程 MainThread 完成,程序就只能按照顺序依次执行。
此程序中,子线程 Thread-1和Thread-2 执行的是 run() 方法中的代码,而 MainThread 执行的是主程序中的代码,它们以快速轮换 CPU 的方式在执行。
守护线程(Daemon Thread)
守护线程(Daemon Thread)也叫后台进程,它的目的是为其他线程提供服务。如果其他线程被杀死了,那么守护线程也就没有了存在的必要。因此守护线程会随着非守护线程的消亡而消亡。Thread类中,子线程被创建时默认是非守护线程,我们可以通过setDaemon(True)将一个子线程设置为守护线程。
# -*- coding: utf-8 -*-import datetimeimport timeimport threadingclass MyThread(threading.Thread): def __init__(self, n): self.n = n # 注意:一定要调用父类的初始化函数,否则无法创建线程 super().__init__() def run(self) -> None: while True: _count = threading.active_count() threading_name = threading.current_thread().getName() times = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") print(f"{times}-{self.n}-"当前活跃的线程个数:{_count}"-"当前线程的名称是":{threading_name}") time.sleep(self.n)for i in range(1, 3): t = MyThread(i) t.setDaemon(True) t.start() print(threading.current_thread().getName()) # 输出2022-12-16 13:27:46-1-"当前活跃的线程个数:2"-"当前线程的名称是":Thread-1MainThread2022-12-16 13:27:46-2-"当前活跃的线程个数:3"-"当前线程的名称是":Thread-2MainThread
将两个子线程改写为守护线程,因为当主程序中的代码执行完后,主线程就可以结束了,这时候被设定为守护线程的两个子线程会被杀死,然后主线程结束。
注意,当前台线程死亡后,Python 解释器会通知后台线程死亡,但是从它接收指令到做出响应需要一定的时间。如果要将某个线程设置为后台线程,则必须在该线程启动之前进行设置。也就是说,将 daemon 属性设为 True,必须在 start() 方法调用之前进行,否则会引发 RuntimeError 异常。
若将两个子线程的其中一个设置为守护线程,另一个设置为非守护线程
# -*- coding: utf-8 -*-import datetimeimport timeimport threadingclass MyThread(threading.Thread): def __init__(self, n): self.n = n # 注意:一定要调用父类的初始化函数,否则无法创建线程 super().__init__() def run(self) -> None: while True: _count = threading.active_count() threading_name = threading.current_thread().getName() times = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") print(f"{times}-{self.n}-"当前活跃的线程个数:{_count}"-"当前线程的名称是":{threading_name}") time.sleep(self.n)for i in range(1, 3): t = MyThread(i) if i == 1: t.setDaemon(True) t.start() print(threading.current_thread().getName()) # 输出2022-12-16 13:30:17-1-"当前活跃的线程个数:2"-"当前线程的名称是":Thread-1MainThread2022-12-16 13:30:17-2-"当前活跃的线程个数:3"-"当前线程的名称是":Thread-2MainThread2022-12-16 13:30:18-1-"当前活跃的线程个数:3"-"当前线程的名称是":Thread-12022-12-16 13:30:19-1-"当前活跃的线程个数:3"-"当前线程的名称是":Thread-12022-12-16 13:30:19-2-"当前活跃的线程个数:3"-"当前线程的名称是":Thread-22022-12-16 13:30:20-1-"当前活跃的线程个数:3"-"当前线程的名称是":Thread-1...
此时非守护线程作为前台程序还在继续执行,守护线程就还有“守护”的意义,就会继续执行。
join()方法
不使用join方法:
当设置多个线程时,在一般情况下(无守护线程,setDeamon=False),多个线程同时启动,主线程执行完,会等待其他子线程执行完,程序才会退出。
# -*- coding: utf-8 -*-import datetimeimport timeimport threadingclass MyThread(threading.Thread): def __init__(self, n): self.n = n # 注意:一定要调用父类的初始化函数,否则无法创建线程 super().__init__() def run(self) -> None: _count = threading.active_count() threading_name = threading.current_thread().getName() times = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") time.sleep(1) print(f"{times}-{self.n}-"当前活跃的线程个数:{_count}"-"当前线程的名称是":{threading_name}")start_time = time.time()print(f"{start_time},这是主线程:", threading.current_thread().name)for i in range(5): t = MyThread(i) # t.setDaemon(True) t.start() # t.join()end_time = time.time()print(f"{end_time},主线程结束了!", threading.current_thread().name)print("一共用时:", end_time - start_time)# 输出1671174404.6552384,这是主线程: MainThread1671174404.656239,主线程结束了! MainThread一共用时: 0.00100064277648925782022-12-16 15:06:44-0-"当前活跃的线程个数:2"-"当前线程的名称是":Thread-12022-12-16 15:06:44-1-"当前活跃的线程个数:3"-"当前线程的名称是":Thread-22022-12-16 15:06:44-2-"当前活跃的线程个数:4"-"当前线程的名称是":Thread-32022-12-16 15:06:44-3-"当前活跃的线程个数:5"-"当前线程的名称是":Thread-42022-12-16 15:06:44-4-"当前活跃的线程个数:6"-"当前线程的名称是":Thread-5
我们的计时是对主线程计时,主线程结束,计时随之结束,打印出主线程的用时。主线程的任务完成之后,主线程随之结束,子线程继续执行自己的任务,直到全部的子线程的任务全部结束,程序结束。
使用join()方法:
主线程任务结束之后,进入阻塞状态,一直等待调用join方法的子线程执行结束之后,主线程才会终止。下面的例子是让t调用join()方法。
# -*- coding: utf-8 -*-import datetimeimport timeimport threadingclass MyThread(threading.Thread): def __init__(self, n): self.n = n # 注意:一定要调用父类的初始化函数,否则无法创建线程 super().__init__() def run(self) -> None: _count = threading.active_count() threading_name = threading.current_thread().getName() times = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") time.sleep(1) print(f"{times}-{self.n}-"当前活跃的线程个数:{_count}"-"当前线程的名称是":{threading_name}")start_time = time.time()print(f"{start_time},这是主线程:", threading.current_thread().name)for i in range(5): t = MyThread(i) # t.setDaemon(True) t.start() t.join()end_time = time.time()print(f"{end_time},主线程结束了!", threading.current_thread().name)print("一共用时:", end_time - start_time)# 输出1671174502.0245655,这是主线程: MainThread2022-12-16 15:08:22-0-"当前活跃的线程个数:2"-"当前线程的名称是":Thread-12022-12-16 15:08:23-1-"当前活跃的线程个数:2"-"当前线程的名称是":Thread-22022-12-16 15:08:24-2-"当前活跃的线程个数:2"-"当前线程的名称是":Thread-32022-12-16 15:08:25-3-"当前活跃的线程个数:2"-"当前线程的名称是":Thread-42022-12-16 15:08:26-4-"当前活跃的线程个数:2"-"当前线程的名称是":Thread-51671174507.0313594,主线程结束了! MainThread一共用时: 5.006793975830078Process finished with exit code 0
join()方法的timeout参数
join的语法结构为join(timeout=None)
,可以看到join()方法有一个timeout参数,其默认值为None,而参数timeout可以进行赋值,其含义是指定等待被join的线程的时间最长为timeout秒,也就是说当在timeout秒内被join的线程还没有执行结束的话,就不再进行等待了。
# -*- coding: utf-8 -*-import datetimeimport timeimport threadingclass MyThread(threading.Thread): def __init__(self, n): self.n = n # 注意:一定要调用父类的初始化函数,否则无法创建线程 super().__init__() def run(self) -> None: _count = threading.active_count() threading_name = threading.current_thread().getName() times = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") time.sleep(5) print(f"{times}-{self.n}-"当前活跃的线程个数:{_count}"-"当前线程的名称是":{threading_name}")start_time = time.time()print(f"{start_time},这是主线程:", threading.current_thread().name)for i in range(5): t = MyThread(i) # t.setDaemon(True) t.start() t.join(2)end_time = time.time()print(f"{end_time},主线程结束了!", threading.current_thread().name)print("一共用时:", end_time - start_time)# 输出1671175114.663927,这是主线程: MainThread2022-12-16 15:18:34-0-"当前活跃的线程个数:2"-"当前线程的名称是":Thread-12022-12-16 15:18:36-1-"当前活跃的线程个数:3"-"当前线程的名称是":Thread-22022-12-16 15:18:38-2-"当前活跃的线程个数:4"-"当前线程的名称是":Thread-31671175124.6681008,主线程结束了! MainThread一共用时: 10.0041737556457522022-12-16 15:18:40-3-"当前活跃的线程个数:4"-"当前线程的名称是":Thread-42022-12-16 15:18:42-4-"当前活跃的线程个数:4"-"当前线程的名称是":Thread-5Process finished with exit code 0
线程锁
# -*- coding: utf-8 -*-import datetimeimport threadingimport timenumber = 0def add(): global number # global声明此处的number是外面的全局变量number for _ in range(10000000): # 进行一个大数级别的循环加一运算 number += 1 times = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") print(f"{times}-"当前活跃的线程个数:{threading.active_count()}"") print("子线程%s运算结束后,number = %s" % (threading.current_thread().getName(), number)) print("------------------------------")for i in range(2): # 用2个子线程,就可以观察到脏数据 t = threading.Thread(target=add) t.start()time.sleep(2) # 等待2秒,确保2个子线程都已经结束运算。print("主线程执行完毕后,number = ", number)# 输出2022-12-20 13:13:05-"当前活跃的线程个数:3"子线程Thread-1运算结束后,number = 11966305------------------------------2022-12-20 13:13:05-"当前活跃的线程个数:2"子线程Thread-2运算结束后,number = 12272268------------------------------主线程执行完毕后,number = 12272268
这里创建两个子线程操作同一个全局变量number,number被初始化为0,两个子线程通过for循环对这个number进行+1,每个子线程循环10000000次,两个子线程同时进行。如果一切正常的话,最终这个number会变成20000000,然而现实并非如此。
可以很明显地看出脏数据的情况。这是因为两个线程在运行过程中,CPU随机调度,你算一会我算一会,在没有对number进行保护的情况下,就发生了数据错误注意此时两个线程是同时开启的。
若是使用了join()的方法
# -*- coding: utf-8 -*-# ...for i in range(2): # 用2个子线程,就可以观察到脏数据 t = threading.Thread(target=add) t.start() t.join() # 添加这一行就让两个子线程变成了顺序执行time.sleep(2) # 等待2秒,确保2个子线程都已经结束运算。print("主线程执行完毕后,number = ", number)# 输出2022-12-20 13:16:02-"当前活跃的线程个数:2"子线程Thread-1运算结束后,number = 10000000------------------------------2022-12-20 13:16:03-"当前活跃的线程个数:2"子线程Thread-2运算结束后,number = 20000000------------------------------主线程执行完毕后,number = 20000000
虽然结果是对的,但是这样的本质是把多线程变成了单线程,失去了多线程的意义。
互斥锁Lock
互斥锁是一种独占锁,同一时刻只有一个线程可以访问共享的数据。使用很简单,初始化锁对象,然后将锁当做参数传递给任务函数,在任务中加锁,使用后释放锁。
# -*- coding: utf-8 -*-import datetimeimport threadingimport timenumber = 0lock = threading.Lock()def add(lk): global number # global声明此处的number是外面的全局变量number lk.acquire() # 开始加锁 for _ in range(10000000): # 进行一个大数级别的循环加一运算 number += 1 times = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") print(f"{times}-"当前活跃的线程个数:{threading.active_count()}"") print("子线程%s运算结束后,number = %s" % (threading.current_thread().getName(), number)) print("------------------------------") lk.release() # 释放锁,让别的线程也可以访问numberfor i in range(2): t = threading.Thread(target=add, args=(lock,)) t.start()time.sleep(2) # 等待2秒,确保2个子线程都已经结束运算。print("主线程执行完毕后,number = ", number)# 输出2022-12-20 13:34:52-"当前活跃的线程个数:3"子线程Thread-1运算结束后,number = 10000000------------------------------2022-12-20 13:34:53-"当前活跃的线程个数:2"子线程Thread-2运算结束后,number = 20000000------------------------------主线程执行完毕后,number = 20000000
RLock可重入锁
用于防止访问共享资源时出现不必要的阻塞。如果共享资源在RLock中,那么可以安全地再次调用它。 RLocked资源可以被不同的线程重复访问,即使它在被不同的线程调用时仍然可以正常工作。
在同一个线程中,RLock.acquire()可以被多次调用,利用该特性,可以解决部分死锁问题。
# -*- coding: utf-8 -*-import threadingnumber = 0# lock = threading.RLock()lock = threading.Lock()def add(lk): global number # global声明此处的number是外面的全局变量number lk.acquire() number += 1 lk.acquire() number += 2 print("子线程%s运算结束后,number = %s" % (threading.current_thread().getName(), number)) lk.release() lk.release()for i in range(2): t = threading.Thread(target=add, args=(lock,)) t.start()
在上面的程序中,两个线程同时尝试访问共享资源number,这里当一个线程当前正在访问共享资源number时,另一个线程将被阻止访问它。 当两个或多个线程试图访问相同的资源时,有效地阻止了彼此访问该资源,这就是所谓的死锁,因此上述程序没有生成任何输出。
# -*- coding: utf-8 -*-import threadingnumber = 0lock = threading.RLock()# lock = threading.Lock()def add(lk): global number # global声明此处的number是外面的全局变量number lk.acquire() number += 1 lk.acquire() number += 2 print("子线程%s运算结束后,number = %s" % (threading.current_thread().getName(), number)) lk.release() lk.release()for i in range(2): t = threading.Thread(target=add, args=(lock,)) t.start()# 输出子线程Thread-1运算结束后,number = 3子线程Thread-2运算结束后,number = 6
这两种锁的主要区别是:RLock允许在同一线程中被多次acquire。而Lock却不允许这种情况。注意:如果使用RLock,那么acquire和release必须成对出现,即调用了n次acquire,必须调用n次的release才能真正释放所占用的锁
Semaphore信号
# -*- coding: utf-8 -*-import datetimeimport threadingimport timesemaphore = threading.BoundedSemaphore(2)def add(n): semaphore.acquire() times = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") _count = threading.active_count() print(f"{times}", f"线程-{n}", f"当前活跃的子线程个数:{_count}") time.sleep(3) semaphore.release()for i in range(1, 10): t = threading.Thread(target=add, args=(i,)) t.start()# 输出2022-12-20 14:39:44 线程-1 当前活跃的子线程个数:22022-12-20 14:39:44 线程-2 当前活跃的子线程个数:32022-12-20 14:39:47 线程-3 当前活跃的子线程个数:82022-12-20 14:39:47 线程-4 当前活跃的子线程个数:82022-12-20 14:39:50 线程-5 当前活跃的子线程个数:62022-12-20 14:39:50 线程-6 当前活跃的子线程个数:62022-12-20 14:39:53 线程-7 当前活跃的子线程个数:42022-12-20 14:39:53 线程-8 当前活跃的子线程个数:42022-12-20 14:39:56 线程-9 当前活跃的子线程个数:2
可以看出用Semaphore来控制后,使得同一个时刻只有两个线程在请求页面虽然当前活跃的子线程个数很多,但真正运行的子线程个数只有两个。
事件Event
Event类会在全局定义一个Flag,当Flag=False时,调用wait()方法会阻塞所有线程;而当Flag=True时,调用wait()方法不再阻塞。形象的比喻就是“红绿灯”:在红灯时阻塞所有线程,而在在绿灯的时候,一次性放行所有排队中的线程。Event类有四个方法:
set():将Flag设置为True
wait():等待
clear():将Flag设置为False
is_set():返回bool值,判断Flag是否为True
# -*- coding: utf-8 -*-import threadingimport timeimport datetimeclass Boss(threading.Thread): def run(self): print("BOSS:伙计们今晚上加班到22:00") event.set() time.sleep(5) # 模拟一个小时这段时间 print("BOSS:22:00了可以下班了") event.set()class Worker(threading.Thread): def run(self): print(f"boss发话了吗:{event.is_set()}") event.wait() # 等待event为真 此列是等待老板发话 times = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") print(f"{times}---woker:命苦啊") time.sleep(1) # 模拟工作中 event.clear() # 清除Event对象内部的信号标志,即将其设为假,此处等待领导发话 event.wait() # Event对象wait的方法只有在内部信号为真的时候才会很快的执行并完成返回。 times = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") print(f"{times}--Woker:OhYeah")if __name__ == "__main__": event = threading.Event() threads = [] for i in range(5): threads.append(Worker()) threads.append(Boss()) for t in threads: t.start() for t in threads: t.join() print("公司下班了")# 输出boss发话了吗:Falseboss发话了吗:Falseboss发话了吗:Falseboss发话了吗:Falseboss发话了吗:FalseBOSS:伙计们今晚上加班到22:002022-12-21 14:12:52---woker:命苦啊2022-12-21 14:12:52---woker:命苦啊2022-12-21 14:12:52---woker:命苦啊2022-12-21 14:12:52---woker:命苦啊2022-12-21 14:12:52---woker:命苦啊BOSS:22:00了可以下班了2022-12-21 14:12:57--Woker:OhYeah2022-12-21 14:12:57--Woker:OhYeah2022-12-21 14:12:57--Woker:OhYeah2022-12-21 14:12:57--Woker:OhYeah2022-12-21 14:12:57--Woker:OhYeah公司下班了
Event的一个好处是:可以实现线程间通信,通过一个线程去控制另一个线程。
condition条件变量
Condition
称作条件锁,依然是通过acquire()/release()加锁解锁。
wait([timeout])
方法将使线程进入Condition的等待池等待通知,并释放锁。使用前线程必须已获得锁定,否则将抛出异常。
notify()
方法将从等待池挑选一个线程并通知,收到通知的线程将自动调用acquire()
尝试获得锁定(进入锁定池),其他线程仍然在等待池中。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。
notifyAll()
方法将通知等待池中所有的线程,这些线程都将进入锁定池尝试获得锁定。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。
Python提供的Condition对象提供了对复杂线程同步问题的支持。Condition被称为条件变量,除了提供与Lock类似的 acquire和release方法外,还提供了wait和notify方法。线程首先acquire一个条件变量,然后判断一些条件。如果条件不满足则 wait;如果条件满足,进行一些处理改变条件后,通过notify方法通知其他线程,其他处于wait状态的线程接到通知后会重新判断条件。不断的重复 这一过程,从而解决复杂的同步问题。
可以认为Condition对象维护了一个锁(Lock/RLock)和一个waiting池。线程通过acquire获得Condition对 象,当调用wait方法时,线程会释放Condition内部的锁并进入blocked状态,同时在waiting池中记录这个线程。当调用notify 方法时,Condition对象会从waiting池中挑选一个线程,通知其调用acquire方法尝试取到锁。
Condition对象的构造函数可以接受一个Lock/RLock对象作为参数,如果没有指定,则Condition对象会在内部自行创建一个RLock。
# -*- coding: utf-8 -*-import threadingimport timecon = threading.Condition()num = 0# 生产者class Producer(threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): # 锁定线程 global num while True: con.acquire() if num >= 5: print("火锅里面里面鱼丸数量已经到达5个,无法添加了!") # 唤醒等待的线程 con.notify() # 唤醒小伙伴开吃啦 con.wait() print("开始添加!!!") num += 1 print("火锅里面鱼丸个数:%s" % str(num)) time.sleep(1) # 释放锁 con.release()# 消费者class Consumers(threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): global num while True: con.acquire() if num <= 0: print("锅底没货了,赶紧加鱼丸吧!") con.notify() # 通知生产鱼丸 con.wait() print("开始吃啦!!!") num -= 1 print("火锅里面剩余鱼丸数量:%s" % str(num)) time.sleep(2) con.release()a = Consumers()b = Producer()a.start()b.start()# 输出锅底没货了,赶紧加鱼丸吧!开始添加!!!火锅里面鱼丸个数:1开始添加!!!火锅里面鱼丸个数:2开始添加!!!火锅里面鱼丸个数:3开始添加!!!火锅里面鱼丸个数:4开始添加!!!火锅里面鱼丸个数:5火锅里面里面鱼丸数量已经到达5个,无法添加了!开始吃啦!!!火锅里面剩余鱼丸数量:4开始吃啦!!!火锅里面剩余鱼丸数量:3开始吃啦!!!火锅里面剩余鱼丸数量:2开始吃啦!!!火锅里面剩余鱼丸数量:1开始吃啦!!!火锅里面剩余鱼丸数量:0锅底没货了,赶紧加鱼丸吧!开始添加!!!火锅里面鱼丸个数:1开始添加!!!火锅里面鱼丸个数:2开始添加!!!火锅里面鱼丸个数:3开始添加!!!火锅里面鱼丸个数:4开始添加!!!火锅里面鱼丸个数:5火锅里面里面鱼丸数量已经到达5个,无法添加了!开始吃啦!!!火锅里面剩余鱼丸数量:4开始吃啦!!!火锅里面剩余鱼丸数量:3开始吃啦!!!火锅里面剩余鱼丸数量:2开始吃啦!!!火锅里面剩余鱼丸数量:1开始吃啦!!!火锅里面剩余鱼丸数量:0锅底没货了,赶紧加鱼丸吧!开始添加!!!火锅里面鱼丸个数:1开始添加!!!火锅里面鱼丸个数:2...
我们可以根据所谓的wait池构建一个带有缓冲区的生产者-消费者模型,即缓冲区好比一个火锅,生产者可以不断生产鱼丸知道火锅装满,然后告知消费者消费,而消费者也可以判断火锅是否空了从而告知生产者继续生产鱼丸:
定时器Timer
通过threading.Timer类可以实现n秒后执行某操作。注意一个timer对象相当于一个新的子线程。
import threadingnum = 0def createTimer(): t = threading.Timer(2, repeat) t.start() if num == 5: t.cancel()def repeat(): global num num += 1 print(num) createTimer()createTimer()# 输出12345
cancel()
函数,可以在定时器被触发前,取消这个Timer。
通过with语句使用线程锁
Python Threading中的Lock模块有acquire()和release()两种方法,这两种方法与with语句的搭配相当于,进入with语句块时候会先执行acquire()方法,语句块结束后会执行release方法。
# -*- coding: utf-8 -*-from threading import Locktemp_lock = Lock()with temp_lock: print(temp_lock)print(temp_lock)# 输出
与之对应的有
# -*- coding: utf-8 -*-from threading import Locktemp_lock = Lock()temp_lock.acquire()try: print(temp_lock)finally: temp_lock.release()print(temp_lock)# 输出
线程池ThreadPoolExecutor
# -*- coding: utf-8 -*-import timeimport threadingfrom concurrent.futures import ThreadPoolExecutor, as_completeddef sayhello(name): print("%s say Hello to %s" % (threading.current_thread().getName(), name)) time.sleep(1) return namename_list = ["张三", "李四", "王二", "麻子"]start_time = time.time()with ThreadPoolExecutor(max_workers=2) as executor: # 创建 ThreadPoolExecutor future_list = [executor.submit(sayhello, name) for name in name_list] # 提交任务for future in as_completed(future_list): result = future.result() # 获取任务结果 print("%s get result : %s" % (threading.current_thread().getName(), result))print("%s cost %d second" % (threading.current_thread().getName(), time.time() - start_time))# 输出ThreadPoolExecutor-0_0 say Hello to 张三ThreadPoolExecutor-0_1 say Hello to 李四ThreadPoolExecutor-0_1 say Hello to 王二ThreadPoolExecutor-0_0 say Hello to 麻子MainThread get result : 王二MainThread get result : 麻子MainThread get result : 李四MainThread get result : 张三MainThread cost 2 second
max_workers参数用来控制线程池中运行的最大线程数
通过submit方法将任务提交到线程池中,一次只能提交一个.submit会立即返回结果,第一个参数是函数名,第二个参数是函数的参数,他是一个元组
submit方法返回是一个future对象
as_completed函数会将运行完的任务一个一个yield出来,它返回任务的结果与提交任务的顺序无关,谁先执行完返回谁
或者
# -*- coding: utf-8 -*-"""线程池的回调"""# -*- coding: utf-8 -*-import timeimport threadingfrom concurrent.futures import ThreadPoolExecutor, as_completeddef sayhello(name): print("%s say Hello to %s" % (threading.current_thread().getName(), name)) time.sleep(1) return namename_list = ["张三", "李四", "王二", "麻子"]start_time = time.time()with ThreadPoolExecutor(max_workers=2) as executor: # 创建 ThreadPoolExecutor for data in executor.map(sayhello, name_list): print("%s get data : %s" % (threading.current_thread().getName(), data)) # 内部迭代中, 每个driver_id开启一个线程print("%s cost %d second" % (threading.current_thread().getName(), time.time() - start_time))# 输出ThreadPoolExecutor-0_0 say Hello to 张三ThreadPoolExecutor-0_1 say Hello to 李四ThreadPoolExecutor-0_1 say Hello to 王二ThreadPoolExecutor-0_0 say Hello to 麻子MainThread get data : 张三MainThread get data : 李四MainThread get data : 王二MainThread get data : 麻子MainThread cost 2 second
使用map方法,无需提前使用submit方法,map方法与python标准库中的map含义相同,都是将序列中的每个元素都执行同一个函数
map方法会将任务运行的结果yield出来
map方法返回任务结果的顺序与提交任务的顺序一致
参考资料-python多线程详解
刘江的博客
- https://www.shibuyu.fun/
关键词:
-
天天看热讯:云时代,MySQL到ClickHouse数据同步产品对比推荐
ClickHouse在执行分析查询时的速度优势很好的弥补了MySQL的不足,但是对于很多开发者和DBA来说,如何将M...
来源: python 多线程详解
天天看热讯:云时代,MySQL到ClickHouse数据同步产品对比推荐
全球焦点!MySQL事务实现原理
“格物致知 叩问苍穹”2023年中国航天踏上新征程
世界热头条丨海南房子合适买吗?《滨江商业广场》优势不足全面解读~
世界今头条!小米13 Ultra被抢购一空!雷军:备货非常困难
全球热点!发改委:下大力气稳定汽车消费 鼓励车企开发乡村“专车”
32.88万起售 全新林肯航海家上市:环绕大屏加持如开星际飞船
环球实时:暗光长焦加持!OPPO Reno10 Pro+来了:堪称“小Find X6”
每日快看:误入“小人国”!华为P60 Pro长焦微距效果惊艳
三星7652机身内存是多少?三星7652参数配置
小米2sc是哪一年上市的?小米2sc参数配置
活性水是什么意思?活性水和直饮水的区别是什么?
红眼病是怎么引起的?红眼病的症状有哪些?
三星笔记本NC10显卡怎么下载?三星笔记本nc10显卡配置
天天观察:香港金管局再承接69.16亿港元沽盘?
打不过啊!本赛季老鹰对绿军0胜5负 场均净负13.2分
国家发展改革委:正在抓紧研究起草关于恢复和扩大消费政策文件
快看点丨极端高温出现 印度一颁奖礼热死11人:每人获赔4.1万
热门:开卷!微软拟自研AI芯片“戴安娜”:降低机器学习成本
黑入iPhone仅需一条iMessage消息!多起NSO“零点击”攻击曝光
米粉首发入手小米13 Ultra:实在太香了 小米11 Pro终于退休了
一体成型无拼接:康巴赫整竹双面砧板69元新低(90元券)
环球热消息:安卓微信暗黑模式怎么设置_设置暗黑模式的方法
环球报道:魏牌全面做强大六/七座市场,“蓝山/高山双旗舰组合”闪耀上海车展
天天热议:软件安全之CRC检测
世界新资讯:六(二)、springMVC数据转换 jacson配置,json中的date由Date格式化输出
[nacos]JAR启动并加载/解析Nacos yml格式的配置文件时,报“java.nio.charset.MalformedInputException:
天天看热讯:山西省政府存量债券柜台市场交易流通业务上线
全国股份转让系统:北京长峰医院因发生重大风险事件,今日起股票停牌
世界快资讯:性能看齐RTX 3060 硬件发烧友打造无风扇显卡
世界热讯:被称为“冰箱杀手”的李斯特菌 到底有多恐怖?
【环球播资讯】小米13 Ultra拆解出炉:后摄几乎占满了主板
全球即时:小米13 Ultra预售秒罄!卢伟冰预言成真:1TB版被抢爆了
79元 小米米家多功能充电台灯开启众筹:一灯多用 秒变手电筒
热头条丨相扑标准动作详解
焦点短讯!4月19日盘前重要公司新闻
天天观速讯丨2023年“泼水节” 西双版纳旅游总收入超21亿元
孔乙己如何再造一个“孔乙己”?
SSD数据不能恢复?或许还有的救
环球视点!最完美的徕卡镜头!小米13 Ultra摄影套装版图赏
新华时评·首季经济形势|巩固企稳回升态势 推动经济运行持续整体好转
观察:实例讲解Playwright(二)
Mysql中的数据类型注意事项
观察:新华时评·首季经济形势|巩固企稳回升态势 推动经济运行持续整体好转
播报:美股异动 | 爱立信(ERIC.US)跌超7% 警告成熟市场5G支出将继续缩减
天天观焦点:哈尔滨还要往北300公里 我国在建最北高铁站取得新进展
五一假期返程火车票今日开抢:千万别忘记
【环球报资讯】239元起!小米发布13大新品 价格信息这里一文看
当前观点:今天小米6发售6周年!雷军推荐“钉子户”升级小米13系列
焦点热讯:关于增加东莞银行股份有限公司为东方人工智能主题混合型证券投资基金销售机构同时开通定投及转换业务的公告
今日热门!读SQL进阶教程笔记12_地址与三值逻辑
环球新消息丨312国道宁镇段快速化改造工程进入基层施工新阶段
世界信息:4月18日基金净值:招商瑞泰1年持有混合A最新净值1.0166,涨0.01%
环球焦点!俄罗斯石油被抢:沙特和阿联酋低价购入 再高价卖给欧洲
松下空调怎么样调睡眠模式 松下空调怎么样
Typora 1.5.12 (windows/MacOs版) 简单高效且实用的Markdown编辑器
环球速递!对JSP(Java Server Pages)的一些理解
关注一季度四川民生数据③丨一季度,四川CPI同比上涨1.2%
一图看懂九号电动远行者F90M:新国标能上牌 90km长续航
全球视点!3299元起 锐龙7000X3D游戏神U战未来:AMD、微软合作优化Win系统
199元!小米游戏手柄发布:支持Steam、三模连接
热门:2023刚过了100多天 从主流到高端手机都如何卷?
环球讯息:手慢无?苹果这些机型或许会在iPhone 15系列推出后停产
最新资讯:不拘一格降人才啥意思_不拘一格降人才
宣传好 贯彻好 实施好——甘肃省电力事体制改革势在必行
世界观察:三星s6818处理器_三星6818
焦点关注:郓城农商银行营业部:开展《反电信网络诈骗法》宣传活动
环球看点!择天记第二季
大专院校包括本科不_大专院校包括本科吗?
【环球新视野】纳税太贵玩家拒领终身XGP 日网假图令晓美焰风评被害
中国铁塔与苏交科战略合作签约仪式举行
北京长峰医院住院部东楼发生火情,情况通报
当前关注:马斯克官宣:将推出AI平台TruthGPT;五粮液成立新能源投资公司?工作人员:正在考察光伏和储能方面丨大公司动态
当前热讯:2023医学考博英语常见的内科词汇:泌尿及男性生殖系统
全球今热点:全面打响夏粮丰产丰收攻坚战
环球即时:04月19日周三机会早知道
世界微头条丨医疗纠纷投诉处理流程
【时快讯】铁塔能源联手品胜电子 推出全球首款电变分离式储能电源
信息:亚马逊加速欧洲扩张卖家只需点两下即可上架全部欧盟国家:亚马逊宣布推出欧洲急速拓展计划(EEA),卖家只需进行两次点击,即可将其业务快速拓展至亚马逊的9大欧
每日速读!Elasticsearch搜索功能的实现(一)--搜索引擎为什么选ES
高起_关于高起简述
最新快讯!饲养员拍打大熊猫被停工 多人求情:网友质疑处罚太重
全球播报:信贷紧缩与衰退预期急剧升温!华尔街最准策略师:极度悲观=风险资产反转信号
【天天热闻】“博客书记”张新实被逮捕,71 岁高龄因贪腐问题被查
当前视讯!02 设置工作环境与环境(一)
环球即时看!不坑盒子(Office,WPS接入人工智能)助你高效办公,掐点准时下班回家。
2023天津春秋喜乐汇相声观演指南
天天即时:环境保护局_关于环境保护局介绍
5999元起 安卓影像之王小米13 Ultra发布:高通发文祝贺
观察:499元 小米米家皮皮灯发布:支持手势操控
腾讯自研芯片“沧海”斩获8大全球第一:远超行业GPU
2600岁“古楠木王”被盗割:11人判刑、修复花费29万元
全球今头条!迷你主机卖到白菜价 零刻12代4核CPU准系统688元
天天微动态丨教你用Python画哆啦A梦、海绵宝宝、皮卡丘、史迪仔!
快资讯:R数据分析:生存数据的预测模型建立方法与评价
天天最新:[快讯]创世达公布2022年年度分红实施方案
环球关注:魔域手游直播(魔域手游蜘蛛刷新点)
淘汰8GB内存 小米13 Ultra售价仅5999元:良心升级不涨价
环球微资讯!小米13 Ultra首发环形冷泵散热:温控甩开一众友商