GIL GIOBAL Interpreter Lock
全局解释器锁
锁就是线程里面那个锁
锁是为了避免资源竞争造成数据的错乱
Python程序的执行过程
1.启动解释器进程 Python.exe
2.解析你的py文件并执行它
每一个py程序中都必须有就是一堆代码其他的解释器参与 解释器
相当于多个线程要调用同一个解释器代码 共享以免竞争 竞争就要出事
给解释器加互斥锁
Python 中 内存管理依赖于 GC (一段用于回收内存的代码)也需要一个线程
除了你自己开的线程 系统还有一些内置线程 就算你的代码不会去竞争解释器 内置线程有可能会竞争
所以必须加锁
当一个线程遇到了io 同时解释器也会自动解锁 去执行其他线程 CPU会切换到其他程序
x = obj +1 a = obj +1 2 x = None -1 a = None -1 0
1.关于GIL性能的讨论:
解释器加锁以后
将导致所有线程只能并发 不能达到真正的并行 意味着同一时间只有一个CPU在处理你的线程
给你的感觉序列低
代码执行有两种状态
阻塞i/o失去CPU的执行权 (CPU等待io完成)
非阻塞 代码注册执行 比如循环一千万次 中途CPU可能切换 很快回来 (CPU在计算)
假如有32核CPU 要处理一个下载任务 网络速度慢 100/s 文件大小为1024kb
如果你的代码中io操作非常多 CPU性能不能直接决定你的任务处理速度
案例:
目前有三个任务 每个任务处理需要一秒 获取元数据需要一小时
3个CPU 需要一小时1秒
1个CPU 需要 一小时3秒
在io密集的程序中 CPU性能无法直接决定程序的执行速度 Python就应该干这种活
在计算密集的程序中 CPU性能可以直接决定程序的执行速度
计算密集测试:
计算密集任务 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)
io密集测试:
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与互斥锁:
t1 = Thread(target=task,) t2 = Thread(target=task,) t1.start() t2.start() t1.join() t2.join() print(num)
GIL 和自定义互斥锁的区别
全局锁不能保证自己开启的线程安全 但是不叫解释器中的数据安全的
GIL 在线程用解释器时 自动加锁 在io阻塞时或线程代码执行完毕时 自动解锁
TCP客户端:
import socket c = socket.socket() c.connect(("127.0.0.1", 65535)) while True: msg = input(">>>:") c.send(msg.encode("utf-8")) data = c.recv(1024) print(data.decode("utf-8"))
进程池:
进程池就是一个装进程的容器
为什么出现
当进程很多的时候方便管理进程
什么时候用?
当并发量特别大的时候 例如双十一
很多时候进程是空闲的 就让他进入进程池 让有任务处理时才从进程池取出来使用
进程池使用
ProcessPooIExecutor类
创建时指定最大进程数 自动创建进程
调用submit函数将任务提交到进程池中
创建进程是在调用submit后发生的
总结:
进程池可以自动创建进程
进程限制最大数
自动选择一个空闲的进程帮你错了任务
进程什么时候算空闲
代码执行完是空闲
IO密集时 用线程池
计算密集时 用进程池
import socket from multiprocessing import Process from concurrent.futures import ProcessPoolExecutor 收发数据 def task(c, addr): while True: try: data = c.recv(1024) print(data.decode("utf-8")) if not data: c.close() break c.send(data.upper()) except Exception: print("连接断开") c.close() break if __name__ == '__main__': server = socket.socket() server.bind(("127.0.0.1",65535)) server.listen(5) 创建一个进程池 默认为CPU个数 pool = ProcessPoolExecutor() while True: c,addr = server.accept() p = Process(target=task,args=(c,addr)) p.start() pool.submit(task,c,addr)
进程什么时候算是空闲:
from concurrent.futures import ProcessPoolExecutor import os,time,random def task(): time.sleep(random.randint(1,2)) print(os.getpid()) def run(): pool = ProcessPoolExecutor(2) for i in range(30): pool.submit(task) if __name__ == '__main__': run()
线程池:
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor from threading import current_thread import os,time,random def task(): time.sleep(random.randint(1,2)) print(current_thread()) def run(): # 默认为cpu核心数 * 5 pool = ThreadPoolExecutor(3) for i in range(30): pool.submit(task) if __name__ == '__main__': run()
GIL图
test:
import time,os print(os.getpid()) time.sleep(100)
test2:
from threading import Thread,Lock def task(): print("咨子线程") Thread(target=task).start() Thread(target=task).start() Thread(target=task).start() 当一个py启动后 会先执行主线程中的代码 在以上代码中又开启了子线程 子线程的任务还是执行代码 解释器在一个进程中只有一个(解释器也是一堆代码) 主线和子线都要去调用解释器的代码 那就产生了竞争关系 my_GIL = Lock() 我的解释器 def my_inerpreter(code_str): my_GIL.acquire() print("执行代码!!!!") print(code_str) my_GIL.release() Thread(target=my_inerpreter,args=("print('1')",)).start() Thread(target=my_inerpreter,args=("print('2')",)).start() Thread(target=my_inerpreter,args=("print('3')",)).start()