线程理论
一台计算机相当于一个工厂,工厂里有很多车间(进程),车间里面有很多工人(线程)。 真正干活儿的是工人(线程)。
1.什么是线程
进程是资源分配的最小单位,而线程是进程中的一部分,每个进程中至少有一个线程,是CPU调度的最小单位。
进程是资源分配的最小单位,线程是CPU调度的最小单位线程开销更小,更轻量级。
2.线程的数据是共享的
在操作系统中,每一个进程的内存空间是独立的,数据不共享。
但是同一个进程中的各个线程,数据是共享的
二:开启线程的2种方式
1.第一种 - 普通方式
from threading import Threadimport timedef task(t_name, s_time):print(f'子线程[{t_name}]开始')time.sleep(s_time)print(f'子线程[{t_name}]结束')if __name__ == '__main__':t1 = Thread(target=task, args=('t1', 1)) # 实例化得到一个线程对象t1.start() # 对象.start()启动线程print('主线程')# 子线程[t1]开始# 主线程# 子线程[t1]结束
2.第二种 - 继承类的方式
from threading import Threadimport timeclass MyThread(Thread):def run(self):print('子线程开始')time.sleep(1)print('子线程结束')if __name__ == '__main__':t = MyThread()t.start()print('主线程')# 子线程开始# 主线程# 子线程结束
三:用多线程实现TCP服务端并发效果
服务端:
from socket import *from threading import Threadserver = socket(AF_INET, SOCK_STREAM)server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)server.bind(('127.0.0.1', 8080))server.listen(5)def talk(conn, client_addr):while True:try:print(f'{client_addr} connected.')msg = conn.recv(1024)if not msg:breakconn.send(msg.upper())except Exception:breakif __name__ == '__main__':while True:conn, client_addr = server.accept()t = Thread(target=talk, args=(conn, client_addr))t.start()
客户端:
from socket import *client = socket(AF_INET, SOCK_STREAM)client.connect(('127.0.0.1', 8080))while True:msg = input('Input your message: ').strip()if not msg:continueclient.send(msg.encode('UTF-8'))msg = client.recv(1024)print(msg.decode('UTF-8'))
四:线程对象join方法 - 等待子线程执行结束
from threading import Threadimport timedef task(t_name, s_time):print(f'{t_name} 开始')time.sleep(s_time)print(f'{t_name} 结束')if __name__ == '__main__':t1 = Thread(target=task, args=('t1', 1))t2 = Thread(target=task, args=('t2', 2))t1.start()t2.start()t1.join() # 等待子线程执行结束t2.join()print('主线程')# t1 开始# t2 开始# t1 结束# t2 结束# 主线程
五:同一个进程下的多个线程数据共享
from threading import Threadimport timemoney = 99def task(t_name, n):global moneyprint(f'{t_name} 开始')money = nprint(f'{t_name} 结束')if __name__ == '__main__':t1 = Thread(target=task, args=('t1', 10))t2 = Thread(target=task, args=('t2', 100))t1.start()t2.start()t1.join()t2.join()print(money)print('主线程')# t1 开始# t1 结束# t2 开始# t2 结束# 100# 主线程
六:线程对象 及 其他方法
from threading import Thread, current_thread, active_countimport timedef task():print('子线程开始')print(current_thread().name) # 打印出当前线程名字time.sleep(1)print('子线程结束')if __name__ == '__main__':t1 = Thread(target=task, name='I\'m Thread1')t2 = Thread(target=task, name='I\'m Thread2')t1.start()t2.start()print(f'存活线程数:{active_count()}') # 打印出当前有多少存活的线程# 子线程开始# I'm Thread1# 子线程开始# I'm Thread2# 存活线程数:3# 子线程结束# 子线程结束from threading import Thread, current_thread, active_countimport timeimport osdef task(t_name, n):print(f'{t_name}开始')print(f'当前线程:{current_thread().name}') # 线程名字print(f'当前线程所在的进程ID:{os.getpid()}')time.sleep(n)print(f'{t_name}结束')if __name__ == '__main__':t1 = Thread(target=task, name='Thread1', args=('t1', 2))t2 = Thread(target=task, name='Thread2', args=('t2', 5))t1.start()t2.start()t1.join()print(f'线程t1是否存活:{t1.is_alive()}')print(f'线程t2是否存活:{t2.is_alive()}')# 当作线程id号print(f'线程t1的ID:{t1.ident}')print(f'线程t2的ID:{t2.ident}')print(f'当前线程所在的进程ID:{os.getpid()}')print(f'存活线程数:{active_count()}') # 打印出2 ,存活的是t2和主线程# t1开始# 当前线程:Thread1# 当前线程所在的进程ID:37420# t2开始# 当前线程:Thread2# 当前线程所在的进程ID:37420# t1结束# 线程t1是否存活:False# 线程t2是否存活:True# 线程t1的ID:45360# 线程t2的ID:73396# 当前线程所在的进程ID:37420# 存活线程数:2# t2结束
知识点:
1.获取线程名:t.name、t.getName(),(name是人为设置的,有默认值:Thread - X)
2.查看当前进程下有几个线程存活:active_count()
3.查看当前线程是否存活:t1.is_alive()
4.查看线程ID:t1.ident
5.查看当前线程所在的进程ID:os.getpid()
七:守护线程
from threading import Thread, current_thread, active_countimport timedef task(t_name, n):print(f'{t_name}开始')time.sleep(n)print(f'{t_name}结束')if __name__ == '__main__':t1 = Thread(target=task, name='Thread1', args=('Thread1', 10))t2 = Thread(target=task, name='Thread2', args=('Thread2', 4))t1.setDaemon(True)t1.start()t2.start()print('主线程')# Thread1开始# Thread2开始# 主线程# Thread2结束
八:线程互斥锁
1.不加互斥锁
多个线程同时读写一个资源,会出现数据错乱
from threading import Thread, Lockimport timemoney = 99def task():global moneytemp = moneytime.sleep(1)money = temp - 1print(money)if __name__ == '__main__':ll = []for i in range(10):t = Thread(target=task)t.start()ll.append(t)for i in ll:i.join()print(f'Final: {money}')# 98# 989898# 98# 98## 98# 98# 98## 98# Final: 98
2.加上互斥锁
将并发变成串行,牺牲了效率,但是保证了数据的安全性
from threading import Thread, Lockimport timemoney = 99def task(n, mutex):global moneymutex.acquire() # 在修改数据的时候,加锁temp = money# time.sleep(random.randint(1, 2))time.sleep(0.1)money = temp - 1mutex.release() # 修改完之后,释放锁,其他线程就可以抢到锁print(money)if __name__ == '__main__':ll = []mutex = Lock()for i in range(10):t = Thread(target=task, args=(i, mutex))t.start()# t.join() # 不能在这里join,会变成串行ll.append(t)for i in ll:i.join()print(f'Final: {money}')# 98# 97# 96# 95# 94# 93# 92# 91# 90# 89# Final: 89
3.了解
悲观锁、乐观锁、分布式锁
线程锁和进程锁 都属于 悲观锁
九:GIL 全局解释器锁
1.什么是GIL?
官网解释:
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.) 在CPython中,全局解释器锁(GIL)是一个互斥体,它阻止多个本机线程同时执行Python字节码。cpyth的线程管理不是必需的。(然而,由于GIL的存在,其他特性也越来越依赖于它所执行的保证。)
GIL(Global Interpreter Lock)全局解释器锁
首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。
同样一段代码可以通过CPython,PyPy(执行速度比CPython快),Psyco等不同的Python执行环境来执行,然而因为CPython是大部分环境下默认的Python执行环境。
所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷,所有GIL并不是python的特性,仅仅是因为历史原因在Cpython解释器中难以移除。
2.GIL存在的原因
①CPython在执行多线程的时候并不是线程安全的,所以为了程序的稳定性,加一把全局解释锁,能够确保任何时候都只有一个Python线程执行。
②因为垃圾回收机制(线程)的存在,如果一个定义了一个x=2,如果在一种较为极端的情况下,x已经生成,但是2还没赋值给x,此时的x就会被垃圾回收机制当成垃圾 被回收,为了避免这种情况出现,加了一把全局解释器锁。
3.为什么只有Python有GIL?
因为Python是一门动态语言,是用C编写的解释型语言,解释一行,执行一行。
前面定义了一个变量,不能保证后面一定会用得到。
4.线程释放GIL锁的2种情况
①.遇到I/O操作
例如:发出了HTTP请求,需要等待响应、读写文件…
②Time Tick 到期
Time Tick规定了线程的最长执行时间,超过时间后自动释放GIL锁。
3.单核与多核情况下的多线程任务调度
单核:
由此可以发现:由于GIL机制的存在,单核CPU在同一时刻,只有一个线程在运行。 当线程遇到了
I/O操作或者Time Tick到期的时候,就会释放GIL锁release GIL。 此时,其他2个线程去抢这把锁,抢到之后,线程才能运行。
虽然出现I/O操作和Time Tick到期都会释放GIL锁,但是这2种情况是不一样的:
出现I/O:
如果Thread1出现了IO操作,释放了GIL锁,Thread1会主动放弃抢锁,不再参与这场竞争。
Time Tick到期:
如果Thread1出现了TimeTick到期,释放了GIL锁,Thread1会和其他线程竞争,同时抢这把GIL锁
- 所以,在单核的情况下,CPU的利用率是很高的。
多核:

由此可以发现:Thread1在CPU1上运行,Thread2在CPU2上运行。 GIL是全局的,CPU2上的Thread2需要等待CPU1上的Thread1让出GIL锁,才有可能执行。 如果在多次竞争中,Thread2都胜出,Thread1没有得到GIL锁,意味着CPU1一直是闲置的,无法发挥多核的优势。
所以,有了GIL的存在,CPython的的多线程 其实就是 单线程。
4.针对与cpython解释器,有哪些注意点?
单核:
不管做什么,建议开多线程。
多核:
①针对IO密集型:开多线程,CPU遇到IO就会切换到其他线程执行,
②针对计算密集型:开多进程,能被多个CPU调度执行。
5.既然GIL有缺陷,为什么还有这么多人用呢?
①.python的库多,库都是基于cpython写起来的,其他解释器没有那么多的第三方库(海纳百川,有容乃大)。
②.虽然有这个历史诟病,但是丝毫不影响我们敲代码(金无足赤,人无完人)。
③.代码是一成不变的,但是我们可以利用它的优势,优化它的劣势(取其精华,去其糟粕)
④.Python牛逼!(人生苦短,我用Python)
6.总结
cpython解释器中只有一个全局锁(GIL),线程必须获取到GIL才能执行,我们开的多线程,不管有几个CPU,同一时刻,只能有一个线程在执行(python的多线程,不能利用多核优势)
十:验证GIL锁的存在方式
1.多线程
from threading import Threaddef task():while True:passif __name__ == '__main__':for i in range(16):t = Thread(target=task) # 因为有GIL锁的存在,同一时刻,只有一条线程执行,所以CPU不会满t.start()
2.多进程
from multiprocessing import Processdef task():while True:passif __name__ == '__main__':for i in range(16):t = Process(target=task) # 由于是多进程,进程中的线程会被CPU调度,16个CPU全部在工作,就会跑满t.start()
十一:GIL锁与普通互斥锁的区别
GIL锁并不能保证我们自己的数据安全,自己使用互斥锁才能保证自己的数据安全
from threading import Thread, Lockimport timemutex = Lock()money = 100def task():global moneytemp = money# time.sleep(1)money = temp - 1print(money)if __name__ == '__main__':for i in range(10):t = Thread(target=task)t.start()# t.join()print(money)
十二:IO密集型 和 计算密集型
1.计算密集型
开线程
from threading import Threadimport time# 计算密集型def task():count = 0for i in range(100000000):count += iif __name__ == '__main__':c_time = time.time()l1 = []for i in range(10):t = Thread(target=task)t.start()l1.append(t)for j in l1:j.join()print(time.time() - c_time)# 39.65782141685486
开进程
from multiprocessing import Processimport time# 计算密集型def task():count = 0for i in range(100000000):count += iif __name__ == '__main__':c_time = time.time()l1 = []for i in range(10):t = Process(target=task)t.start()l1.append(t)for j in l1:j.join()print(time.time() - c_time)# 7.2425291538238525
2.IO密集型
开线程
from threading import Threadimport time# 计算密集型def task():time.sleep(2)if __name__ == '__main__':c_time = time.time()l1 = []for i in range(400):t = Thread(target=task)t.start()l1.append(t)for j in l1:j.join()print(time.time() - c_time)# 2.0086498260498047
开进程
from multiprocessing import Processimport time# 计算密集型def task():time.sleep(2)if __name__ == '__main__':c_time = time.time()l1 = []for i in range(400):t = Process(target=task)t.start()l1.append(t)for j in l1:j.join()print(time.time() - c_time)# 7.729241371154785
十三:死锁现象(哲学家就餐问题) 与 递归锁(可重入所)
1.什么是死锁现象
指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁
①出现死锁的情况1
进程1拿到了A锁,等待B锁;
进程2拿到了B锁,等待A锁;
进程1和进程2相互等待,僵持不下。
②出现死锁的情况2
进程1拿到了A锁,进程1再次拿A锁;
此时,A锁已经被进程1拿到,没有释放,无法再次拿A锁,程序凝滞了。
2.死锁实例
from threading import Thread, Lockimport timemutexA = Lock()mutexB = Lock()# print(mutexA is mutexB) # False # 两把锁不同def eat_apple(name):mutexA.acquire()print(f'{name} 获取了A锁')mutexB.acquire()print(f'{name} 获取了B锁')print('开始吃苹果')print('苹果吃完了')mutexB.release()print(f'{name} 释放了B锁')mutexA.release()print(f'{name} 释放了A锁')def eat_egg(name):mutexB.acquire()print(f'{name} 获取了B锁')time.sleep(2)mutexA.acquire()print(f'{name} 获取了A锁')print('开始吃鸡蛋')print('鸡蛋吃完了')mutexA.release()print(f'{name} 释放了A锁')mutexB.release()print(f'{name} 释放了B锁')if __name__ == '__main__':l1 = ['Alan', 'Ben', 'Cindy']for name in l1:t1 = Thread(target=eat_apple, args=(name,))t2 = Thread(target=eat_egg, args=(name,))t1.start()t2.start()# Alan 获取了A锁# Alan 获取了B锁# 开始吃苹果# 苹果吃完了# Alan 释放了B锁# Alan 释放了A锁# Alan 获取了B锁# Ben 获取了A锁# Alan正在第二轮获取到B锁之后,sleep了2秒,A锁被Ben抢走,两者相互等待,出现了死锁现象
3.什么是递归锁?
递归锁(可重入),可以多次acquire,每acquire一次,内部计数器+1,每release一次,内部计数器-1。
可重入:可以被同一个人acquire多次
只有计数器为0,其他人才可以获取这把锁。
4.递归锁 实例
from threading import Thread, RLockimport timemutexA = mutexB = RLock() # 链式复制# print(mutexA is mutexB) # True # 同一把锁def eat_apple(name):mutexA.acquire()print(f'{name} 获取了A锁')mutexB.acquire()print(f'{name} 获取了B锁')print('开始吃苹果')print('苹果吃完了')mutexB.release()print(f'{name} 释放了B锁')mutexA.release()print(f'{name} 释放了A锁')def eat_egg(name):mutexB.acquire()print(f'{name} 获取了B锁')time.sleep(2)mutexA.acquire()print(f'{name} 获取了A锁')print('开始吃苹果')print('鸡蛋吃完了')mutexA.release()print(f'{name} 释放了A锁')mutexB.release()print(f'{name} 释放了B锁')if __name__ == '__main__':l1 = ['Alan', 'Ben', 'Cindy']for name in l1:t1 = Thread(target=eat_apple, args=(name,))t2 = Thread(target=eat_egg, args=(name,))t1.start()t2.start()# Alan 获取了A锁# Alan 获取了B锁# 开始吃苹果# 苹果吃完了# Alan 释放了B锁# Alan 释放了A锁# Alan 获取了B锁# Alan 获取了A锁# 开始吃苹果# 鸡蛋吃完了# Alan 释放了A锁# Alan 释放了B锁# Ben 获取了A锁# Ben 获取了B锁# 开始吃苹果# 苹果吃完了# Ben 释放了B锁# Ben 释放了A锁# Ben 获取了B锁# Ben 获取了A锁# 开始吃苹果# 鸡蛋吃完了# Ben 释放了A锁# Ben 释放了B锁# Cindy 获取了A锁# Cindy 获取了B锁# 开始吃苹果# 苹果吃完了# Cindy 释放了B锁# Cindy 释放了A锁# Cindy 获取了B锁# Cindy 获取了A锁# 开始吃苹果# 鸡蛋吃完了# Cindy 释放了A锁# Cindy 释放了B锁
十四:Semaphore信号量
1.什么是信号量?
Semaphore信号量,可以理解为多把锁,同时允许多个线程来更改数据。
2.应用场景
多个人抢占固定的公共场所资源
3.实例
from threading import Thread, Semaphoreimport timeimport randomsm = Semaphore(3) # 数字表示可以同时有多少个线程操作def task(name):sm.acquire()print(f'{name} 正在占用')time.sleep(random.randint(1, 5))sm.release()print(f'{name} 出去了')if __name__ == '__main__':for i in range(10):t = Thread(target=task, args=(f'路人{i}号',))t.start()# 路人0号 正在占用# 路人1号 正在占用# 路人2号 正在占用# 路人1号 出去了# 路人3号 正在占用# 路人3号 出去了# 路人4号 正在占用# 路人0号 出去了# 路人5号 正在占用# 路人2号 出去了# 路人6号 正在占用# 路人5号 出去了# 路人4号 出去了# 路人7号 正在占用# 路人8号 正在占用## 路人8号 出去了# 路人9号 正在占用# 路人7号 出去了# 路人6号 出去了# 路人9号 出去了
十五:Event事件
一些线程需要等到其他线程执行完成之后才能执行,类似于发射信号 比如一个线程等待另一个线程执行结束再继续执行
场景:1个进程读取一个文件的前半部分,读完了之后,发1个信号,另1个进程读取这个文件的后半部分
1.实例1 - 教师抢教室
from threading import Thread, Eventimport timeimport randomevent = Event()def room(name):print(f'教室 {name} 正在被使用')time.sleep(random.randint(1, 3))print(f'教室 {name} 使用完毕,门已打开')event.set()def teacher(name):print(f'{name} 正在等待教室开门')event.wait() # 只要教室门没开,老师就只能在外面等着print(f'门开了 {name} 开始冲刺了')if __name__ == '__main__':r1 = Thread(target=room, args=('Room1',))r1.start()for i in range(5):t = Thread(target=teacher, args=(f'老师{i}',))t.start()# 教室 Room1 正在被使用# 老师0 正在等待教室开门# 老师1 正在等待教室开门# 老师2 正在等待教室开门# 老师3 正在等待教室开门# 老师4 正在等待教室开门# 教室 Room1 使用完毕,门已打开# 门开了 老师0 开始冲刺了# 门开了 老师1 开始冲刺了# 门开了 老师3 开始冲刺了# 门开了 老师2 开始冲刺了# 门开了 老师4 开始冲刺了
2.实例2 - 文件分批读取
a.txt
1234567890
py
from threading import Thread, Eventimport osevent = Event()size = os.path.getsize('a.txt') # 获取文件总大小def read_first():with open('a.txt', mode='r', encoding='UTF-8') as f:n = size // 2 # 整除 取文件的一半data = f.read(n)print(data)print('我已读完一半了,发出信号')event.set() # 文件的一半读取完毕,发送信号def read_last():event.wait() # 等待信号with open('a.txt', mode='r', encoding='UTF-8') as f:n = size // 2 # 整除 取文件的一半f.seek(n, 0) # 光标从文件开头开始,移动了n个字节,移动到了文件的一半data = f.read()print(data)event.set() # 文件的一半读取完毕,发送信号if __name__ == '__main__':t1 = Thread(target=read_first)t2 = Thread(target=read_last)t1.start()t2.start()# 12345# 我已读完一半了,发出信号# 67890
十六:线程Queue
1.进程queue 和 线程queue 不是同一个queue
进程queue
from multiprocessing import Queue
线程queue
from queue import Queue
线程间通信,因为共享变量会出现数据不安全问题,用线程queue通信,不需要加锁,内部自带。
queue是线程安全的
2.三种Queue
- Queue:队列 FIFO 先进先出
- PriorityQueue:优先级队列,谁小 谁先出
- LifoQueue:栈 LIFO 后进先出
3.如何使用
Queue
from queue import Queueq = Queue(5)q.put('Alan')q.put('Ben')q.put('Cindy')q.put('Dad')q.put('Edge')print(q.get()) # Alanprint(q.get()) # Benprint(q.get()) # Cindyprint(q.get()) # Dadprint(q.get()) # Edge# print(q.get()) # 卡住# print(q.get_nowait()) # 不会卡,但是会报错# print(q.full()) # 是否满# print(q.empty()) # 是否空
LifoQueue
from queue import LifoQueueq = LifoQueue(5)q.put('Alan')q.put('Ben')q.put('Cindy')q.put('Dad')q.put('Edge')print(q.get()) # Edgeprint(q.get()) # Dadprint(q.get()) # Cindyprint(q.get()) # Benprint(q.get()) # Alan
PriorityQueue
from queue import PriorityQueueq = PriorityQueue(5)q.put((10, 'Alan'))q.put((100, 'Ben'))q.put((99, 'Cindy'))q.put((1002, 'Dad'))q.put((999, 'Edge'))print(q.get()) # (10, 'Alan')print(q.get()) # (99, 'Cindy')print(q.get()) # (100, 'Ben')print(q.get()) # (999, 'Edge')print(q.get()) # (1002, 'Dad')
十七:线程池
1.为什么会出现 池?
不管是开进程 还是 开线程,都不能无限制地开,通过 池,假设池子里有10个,不管之后再怎么开,永远都是这10个。
2.实例
无池 - 持续不断地输出
from threading import Threadimport timepool = ThreadPoolExecutor(5) # 填写的数字,就是池的大小def task():print('Task Start')time.sleep(2)print('Task Done')if __name__ == '__main__':for i in range(100): # 启动了100个线程t = Thread(target=task)t.start()# Task Start# Task Start# Task Start# Task Start# Task Start# Task Start# Task Start# Task Start# Task Start# Task Start# Task Start# Task Start# Task Start
有池 - 最多只能5个5个输出
from concurrent.futures import ThreadPoolExecutorimport timepool = ThreadPoolExecutor(5) # 填写的数字,就是池的大小def task():print('Task Start')time.sleep(2)print('Task Done')if __name__ == '__main__':for i in range(100): # 启动了100个线程pool.submit(task,)# Task Start# Task Start# Task Start# Task Start# Task Start# Task Done# Task Done# Task Start# Task Done# Task Done# Task Start# Task Done# Task Start# Task Start# Task Start
3.爬虫小案例
from concurrent.futures import ThreadPoolExecutorimport requests # 爬虫会学到的模块(需要自行安装:pip3 install requests)pool = ThreadPoolExecutor(2)def get_pages(url):# https://www.baidu.comres = requests.get(url) # 向这个地址发送请求name = url.rsplit('/')[-1] + '.html'print(name) # www.baidu.com.html# res.content拿到页面的二进制return {'name': name, 'text': res.content}def call_back(f):dic = f.result()with open(dic['name'], 'wb') as f:f.write(dic['text'])if __name__ == '__main__':l1 = ['https://www.baidu.com', 'https://www.bilibili.com', 'https://www.cnblogs.com']for url in l1:pool.submit(get_pages, url).add_done_callback(call_back)
十八:不同线程之间 数据交互的2种方式
1.共享变量
不同线程修改同一份数据需要加锁(互斥锁)
2.通过Queue
不需要考虑数据安全问题(线程安全了)
十九:线程池的shutdown
主线程等待所有任务执行完成之后,再运行
from concurrent.futures import ThreadPoolExecutorimport timepool = ThreadPoolExecutor(3)def task(name):print(f'{name} 开始')time.sleep(1)print(f'{name} 结束')if __name__ == '__main__':for i in range(20):pool.submit(task, f'Use {i}')# 放到for循环外面,等待所有任务执行完成,主线程再继续走pool.shutdown(wait=True) # 等待所有任务完成,并且把池关闭(默认的wait=True)# 问题:关闭之后还能提交任务吗? 不能!!!# pool.submit(task, 'emmmm') # RuntimeError: cannot schedule new futures after shutdownprint('Main') # 立马执行,等20个线程都执行完了,再执行
二十:定时器(线程)
多长时间之后 执行一个任务
Timer(时间, 函数)
from threading import Timerdef task():print('I\'m Darker')if __name__ == '__main__':t = Timer(1, task) # 本质就是开2个线程,延迟1秒执行t.start()
args传值
from threading import Timerdef task(name):print(f'I\'m {name}')if __name__ == '__main__':t = Timer(1, task, args=('Darker',))t.start()
kwargs传值
from threading import Timerdef task(name):print(f'I\'m {name}')if __name__ == '__main__':t = Timer(1, task, kwargs={'name': 'Darker'})t.start()



