一.全局解释器锁
1.GIL:全局解释器锁
GIL本质就是一把互斥锁,是夹在解释器身上的
统一进程内的所有线程都需要先抢到GIL锁,才能执行pai解释器代码
2.GIL优缺点:
优点:
保证Cpython解释器内存管理的线程安全
缺点:
同一进程内所有的线程同一时刻只能有一个执行,
也就是锁Cpython解释器多线程无法实现真正的并行
from threading import Thread,current_thread import time def task(): print("%s is running"%current_thread().name) time.sleep(3) print("%s is done"current_thread().name) if __name__=="__main__": t1=Thread(target=task) t2=Thread(target=task) t3=Thread(target=task) t1.start() t2.start() t3.start()
二.Cpython解释器并发效率验证
关于GIL性能的讨论
解释器加锁以后
将导致所有线程只能并发 不能达到真正的并行 意味着同一时间只有一个CPU在处理你的线程
给你的感觉是效率低
代码执行有两种状态
阻塞 i/o 失去CPU的执行权 (CPU等待IO完成)
非阻塞 代码正常执行 比如循环一千万次 中途CPU可能切换 很快会回来 (CPU在计算)
假如有32核CPU 要处理一个下载任务 网络速度慢 100k/s 文件大小为1024kb
如果你的代码中IO操作非常多 cpu性能不能直接决定你的任务处理速度
案例:
目前有三个任务 每个任务处理需一秒 获取元数据需要一小时
3个CPU 需要 一小时1秒
1个cpu 需要 一小时3秒
在IO密集的程序中 CPU性能无法直接决定程序的执行速度 python就应该干这种活儿
在计算密集的程序中 CPU性能可以直接决定程序的执行速度
#计算密集型测试
from threading import Thread from multiprocessing import Process import time # 计算密集任务 def task1(): sum = 1 for i in range(10000000): sum *= i def task2(): sum = 1 for i in range(10000000): sum *= i def task3(): sum = 1 for i in range(10000000): sum *= i def task4(): sum = 1 for i in range(10000000): sum *= i def task5(): sum = 1 for i in range(10000000): sum *= i def task6(): sum = 1 for i in range(10000000): sum *= i if __name__ == '__main__': # 开始时间 st_time = time.time() # 多线程情况下 # t1 = Thread(target=task1) # t2 = Thread(target=task2) # t3 = Thread(target=task3) # t4 = Thread(target=task4) # t5 = Thread(target=task5) # t6 = Thread(target=task6) t1 = Process(target=task1) t2 = Process(target=task2) t3 = Process(target=task3) t4 = Process(target=task4) t5 = Process(target=task5) t6 = Process(target=task6) t1.start() t2.start() t3.start() t4.start() t5.start() t6.start() # # t1.join() # t2.join() # t3.join() # t4.join() # t5.join() # t6.join() print(time.time() - st_time)
from threading import Thread from multiprocessing import Process import time # 计算密集任务 def task1(): time.sleep(3) def task2(): time.sleep(3) def task3(): time.sleep(3) def task4(): time.sleep(3) def task5(): time.sleep(3) def task6(): time.sleep(3) if __name__ == '__main__': # 开始时间 st_time = time.time() # 多线程情况下 # t1 = Thread(target=task1) # t2 = Thread(target=task2) # t3 = Thread(target=task3) # t4 = Thread(target=task4) # t5 = Thread(target=task5) # t6 = Thread(target=task6) t1 = Process(target=task1) t2 = Process(target=task2) t3 = Process(target=task3) t4 = Process(target=task4) t5 = Process(target=task5) t6 = Process(target=task6) t1.start() t2.start() t3.start() t4.start() t5.start() t6.start() # t1.join() # t2.join() # t3.join() # t4.join() # t5.join() # t6.join() print(time.time() - st_time)
三.GIL与互斥锁
from threading import Thread,Lock import time mutex = Lock() num = 1 def task(): global num # print(num) mutex.acquire() temp = num print(temp) time.sleep(1) # 当你们线程中出现io时 GIL锁就解开 num = temp + 1 mutex.release() # 线程任务结束时GIL锁解开 t1 = Thread(target=task,) t2 = Thread(target=task,) t1.start() t2.start() t1.join() t2.join() print(num)
GIL 和自定义互斥锁的区别
全局锁不能保证自己开启的线程安全 但要保证解释器中的数据的安全
GIL 在线程调用解释器是 自动加锁 在IO阻塞时或线程执行完毕时 自动解锁
四.进程池和线程池
进程池
就是一个装进程的容器
为什么出现
当进程很多的时候方便管理进程
什么时候用?
当并发量特别大的时候 列入双十一
很多时候进程是空闲的 就让他进入进程池 让有任务处理时才从进程取出来使用
进程池使用
ProcessPoolExecutor类
创建时指定最大进程数 自动创建进程
调用submit函数将任务提交进程池中
创建进程是在调用submit后发生
总结一下:
进程池可以自动创造进程
进程现在最大进程数
自动选择一个空闲的进程帮你处理任务
进程什么时候算是空闲?
代码执行完算是空闲
IO密集时 用线程池
计算密集时 用线程池
思考
1、什么是GIL
GIL = Global Interpreter Lock (全局解释锁器锁)
2、有了GIL会对单进程下的多个线程造成什么样的影响
防止对个线程竞争统一资源造成数据的错乱,只能有一个线程进行读写操作.
3、为什么要有GIL
为了避免资源竞争造成的数据错乱
4、GIL与自定义互斥锁的区别,多个线程争抢GIL与自定义互斥锁的过程分析
GIL不保证自己开启线程的安全 但保证解释器中数据的安全
GIL 在线程调用解释其时 自动加锁 在IO阻塞是或线程代码执行完毕时,自动解锁
5、什么时候用python的多线程,什么时候用多进程,为什么?
2、进程池与线程池
1、池的用途,为何要用它
池用来存储线程和进程,可以方便进程和线程的管理
2、池子里什么时候装进程什么时候装线程?
计算密集时装进程,IO密集时装线程
3、基于进程池与线程池实现并发的套接字通信
#服务端 from socket import * from threading import Thread from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor tpool=ThreadPoolExecutor(3) def communicte(conn,client_addr): while True: try: data= conn.recv(1024) if not data:break conn.send(data.upper()) except ConnectionAbortedError: break conn.close() def server(): server = socket(AF_INET,SOCK_STREAM) server.bind(('127.0.0.1',8080)) server.listen(5) while True: conn,client_addr=server.accept() print(client_addr) tpool.submit(communicte,conn,client_addr) server.close() if __name__=='__main__': server()
#客户端 import socket c = socket.socket() c.connect(('127.0.0.1',8080)) while True: msg = input(">>>:") c.send(msg.encode("utf-8")) data = c.recv(1024) print(data.decode("utf-8"))