协程
一、验证GIL的存在
from threading import Thread money = 100 def task(): global money money -= 1 t_list = [] for i in range(100): t = Thread(target=task) t.start() t_list.append(t) # [线程1 线程2 线程3 ... 线程100] for t in t_list: t.join() # 等待所有的线程运行结束 查看money是多少 print(money) ''' 运行结果: 0 按理来说这个100个线程是同时生成的 所以每个线程拿到的money应该都是100运行task的时候应该都是100-1 但是现在却只有0了 说明现在线程肯定是一个一个运行的 所以按照我们之前那说的GIL解释器锁的特性 每个线程都要抢锁才能运行 所以就验证了GIL的存在保护了数据的安全 '''
二、验证GIl的特点
from threading import Thread import time money = 100 def task(): global money temp = money time.sleep(0.1) money = temp - 1 t_list = [] for i in range(100): t = Thread(target=task) t.start() t_list.append(t) # [线程1 线程2 线程3 ... 线程100] for t in t_list: t.join() # 等待所有的线程运行结束 查看money是多少 print(money) ''' 运行结果: 99 现在我们只是在数据前面模拟一个网络延迟数据就会错乱了 因为GIL解释器锁遇到IO存在就会释放锁 然后让其他线程抢 所以数据就错乱 ''' """GIL不会影响程序层面的数据也不会保证它的修改是安全的要想保证得自己加锁""" from threading import Thread,Lock import time money = 100 metex = Lock() def task(): global money metex.acquire() temp = money time.sleep(0.1) money = temp - 1 metex.release() t_list = [] for i in range(100): t = Thread(target=task) t.start() t_list.append(t) # [线程1 线程2 线程3 ... 线程100] for t in t_list: t.join() # 等待所有的线程运行结束 查看money是多少 print(money) ''' 运行结果: 0 想要数据的安全必须要自己加锁 '''
三、验证python多线程的作用
''' 需要分情况 情况1 单个CPU 多个CPU 情况2 IO密集型(代码有IO操作) 计算密集型(代码没有IO) 1.单个CPU IO密集型 多进程 申请额外的空间 消耗更多的资源 多线程 消耗资源相对较少 通过多道技术 ps:多线程有优势!!! 计算密集型 多进程 申请额外的空间 消耗更多的资源(总耗时+申请空间+拷贝代码+切换) 多线程 消耗资源相对较少 通过多道技术(总耗时+切换) ps:多线程有优势!!! 2.多个CPU IO密集型 多进程 总耗时(单个进程的耗时+IO+申请空间+拷贝代码) 多线程 总耗时(单个进程的耗时+IO) ps:多线程有优势!!! 计算密集型 多进程 总耗时(单个进程的耗时) 多线程 总耗时(多个进程的综合) ps:多进程完胜!!! '''
1.就算密集型
from multiprocessing import Process import os import time def work(): # 计算密集型 res = 1 for i in range(1, 100000): res *= i if __name__ == '__main__': # print(os.cpu_count()) # 8 查看当前计算机CPU个数 start_time = time.time() p_list = [] for i in range(8): # 一次性创建8个进程 p = Process(target=work) p.start() p_list.append(p) # [进程0, 进程2,...进程7] for p in p_list: # 确保所有的进程全部运行完毕 p.join() print('总耗时:%s' % (time.time() - start_time)) # 获取总的耗时 ''' 以上代码就是看一下在计算密集型的情况下 多进程需要多久完成 运行结果: 总耗时:6.077852249145508 ''' from threading import Thread import time def work(): # 计算密集型 res = 1 for i in range(1, 100000): res *= i if __name__ == '__main__': start_time = time.time() t_list = [] for i in range(8): # 一次创建8个线程 t = Thread(target=work) t.start() t_list.append(t) # [线程1 线程2 ... 线程7] for t in t_list: # 确保所有的进程全部运行完毕 t.join() print('总耗时:%s' % (time.time() - start_time)) # 获取总的耗时 ''' 上面的代码就是看在计算密集型的代码下多线程需要运行多久 多线程: 总耗时:21.633551359176636 多进程: 总耗时:6.077852249145508 所以我们可以很明显的看出 在计算下密集型中 多进程效率更高 能充分利用多核 '''
2.IO密集型
from multiprocessing import Process from threading import Thread import time def work(): time.sleep(2) # 模拟纯IO操作 if __name__ == '__main__': start_time = time.time() p_list = [] for i in range(100): # 一次性创建100个进程 p = Process(target=work) p.start() p_list.append(p) # [进程0, 进程2,...进程100] for p in p_list: # 确保所有进程都运行完了 p.join() print('总耗时:%s' % (time.time() - start_time)) ''' 以上代码就是在看IO密集型的情况下多进程的运行时间 运行结果: 总耗时:4.354898691177368 ''' def work(): time.sleep(2) # 模拟纯IO操作 if __name__ == '__main__': start_time = time.time() t_list = [] for i in range(100): # 一次性创建100个线程 t = Thread(target=work) t.start() t_list.append(t) # [线程1 线程2 ... 线程7] for t in t_list: # 确保每个线程都运行完了 t.join() print('总耗时:%s' % (time.time() - start_time)) ''' 以上代码就是在看IO密集型的情况下多线程的运行时间 多线程: 总耗时:2.0221118927001953 多进程: 总耗时:4.354898691177368 所以我们可以看出在IO密集型的情况下多线程和还是比多进程块的在量大的情况下更明显 '''
四、死锁现象
""" 虽然我们已经掌握了互斥锁的使用 先抢锁 后释放锁 但是在实际项目尽量少用(你也不会用!!!) """ from threading import Thread, Lock import time mutexA = Lock() # 类名加括号每执行一次就会产生一个新的对象 mutexB = Lock() # 就是产生了两把锁 class MyThread(Thread): def run(self): self.func1() self.func2() def func1(self): mutexA.acquire() print(f'{self.name}抢到了A锁') mutexB.acquire() print(f'{self.name}抢到了B锁') mutexB.release() print(f'{self.name}释放了B锁') mutexA.release() print(f'{self.name}释放了A锁') def func2(self): mutexB.acquire() print(f'{self.name}抢到了B锁') time.sleep(1) mutexA.acquire() print(f'{self.name}抢到了A锁') mutexA.release() print(f'{self.name}释放了A锁') mutexB.release() print(f'{self.name}释放了B锁') for i in range(10): t = MyThread() t.start() ''' Thread-1抢到了A锁 Thread-1抢到了B锁 Thread-1释放了B锁 Thread-1释放了A锁 Thread-1抢到了B锁 Thread-2抢到了A锁 这就是死锁现象 现在有10个线程 当线程1抢到A锁时其它线程都不能在运行了只能等线程1释放锁的时候才能继续抢锁 而当线程1释放A锁的时候 其他线程就会立马抢A锁 抢到A锁的时候就会继续运行代码 抢B锁 而现在的B锁在线程1的手上 就会卡住 而线程1要抢的A锁现在 在其他线程的手上 所以现在两个线程互相都抢不到锁和释放锁就会进入死锁状态 让代码卡死 '''
五、信号量
信号量本质也是互斥锁 只不过它是多把锁 """ 强调: 信号量在不同的知识体系中 意思可能有区别 在并发编程中 信号量就是多把互斥锁 在django中 信号量指的是达到某个条件自动触发(中间件) ... """ ''' 我们之前使用Lock产生的是单把锁 类似于单间厕所 信号量相当于一次性创建多间厕所 类似于公共厕所 ''' from threading import Thread, Lock, Semaphore import time import random sp = Semaphore(5) # 一次性产生五把锁 class MyThread(Thread): def run(self): sp.acquire() print(self.name) time.sleep(random.randint(1, 3)) sp.release() for i in range(20): t = MyThread() t.start() ''' 意思就是现在有20个线程 一起抢五把锁前五个抢到锁的 运行代码 然后在释放锁的前面我们模拟了网络延迟 所以每个线程的释放时间时不一样的 但是也是一旦释放锁 其他线程就会去抢锁 然后运行代码 所以同一时间只有五个线程在运行代码 '''
六、event事件
''' 子进程\子线程之间可以彼此等待彼此 eg: 子A运行到某一个代码位置后发信号告诉子B开始运行 其实就是子B在等待子A给信号 等到了这个信号子B才开始运行 ''' from threading import Thread, Event import time event = Event() # 类似于造了一个红绿灯 def light(): print('红灯亮着的 所有人都不能动') time.sleep(3) print('绿灯亮了 油门踩到底 给我冲!!!') event.set() # 当单独的线程运行到这时 就会给其他线程给信号让其他线程运行代码 def car(name): print('%s正在等红灯' % name) event.wait() # 线程如果运行到这其实就是在等待信号 其他线程一当给了信号 wait就会接受信号 开始运行代码 print('%s加油门 飙车了' % name) t = Thread(target=light) # 单独创一个线程 t.start() for i in range(20): # 一次性创20个线程 t = Thread(target=car, args=('熊猫PRO%s' % i,)) t.start()
七、进程池与线程池
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor from threading import current_thread import os import time pool = ThreadPoolExecutor(5) # 固定产生五个线程 # pool = ProcessPoolExecutor(5) # 固定产生五个线程 def task(n): print(current_thread().name) # 获取线程名 # print(os.getpid()) # 这个是获取进程号 # print(n) time.sleep(1) return '返回的结果' # 这个就是返回异步操作的自动结果 def func(*args, **kwargs): # print('func', args, kwargs) print(args[0].result()) if __name__ == '__main__': for i in range(20): # 一次性产生20个进程或20个线程 # res = pool.submit(task,123) # 朝池子中提交任务(异步) # print(res.result()) # 同步 如果获取结果单独运行 那么就会让程序变成同步操作效率会变慢 # pool.submit(task, 123).add_done_callback(func) """异步回调:异步任务执行完成后有结果就会自动触发该机制""" pool.submit(task, 123).add_done_callback(func) ''' 因为异步不是自动获取结果嘛 现在怎么获取呢? 这就需要异步回调 add_done_callback() 当线程或进程运行程序的时候我们让task代替 异步操作会提交结果 而现在异步回调会接受结果 然后交给括号内的函数执行 然后通过args[0].result()获取结果 '''
八、协程
''' 进程:资源单位 线程:执行单位 协程:单线程下实现并发(效率极高) 在代码层面欺骗CPU 让CPU觉得我们的代码里面没有IO操作 实际上IO操作被我们自己写的代码检测 一旦有 立刻让代码执行别的 (该技术完全是程序员自己弄出来的 名字也是程序员自己起的) 核心:自己写代码完成切换+保存状态 ''' # 而想要通过代码编写进程需要导入模块 gevent import time from gevent import monkey;monkey.patch_all() # 固定编写 用于检测所有的IO操作(猴子补丁) 不用格式化代码 from gevent import spawn def func1(): print('func1 running') time.sleep(3) print('func1 over') def func2(): print('func2 running') time.sleep(5) print('func2 over') if __name__ == '__main__': start_time = time.time() func1() func2() print(time.time() - start_time) ''' 如果这是一个线程执行上面的代码的话 效率肯定是很慢的 运行结果: func1 running func1 over func2 running func2 over 8.174297332763672 ''' # 协程: import time from gevent import monkey;monkey.patch_all() # 固定编写 用于检测所有的IO操作(猴子补丁) 不用格式化代码 from gevent import spawn def func1(): print('func1 running') time.sleep(3) # 当线程运行到IO操作的时候 spawn就会让程序跳到其他地方欺骗CPU不要让CCPU切走 print('func1 over') def func2(): print('func2 running') time.sleep(5) print('func2 over') if __name__ == '__main__': start_time = time.time() s1 = spawn(func1) s2 = spawn(func2) s1.join() s2.join() print(time.time() - start_time) ''' 而如果是写程的话代码执行结果就是其中一个线程执行效率的最慢值: 运行结果: func1 running func2 running func1 over func2 over 5.024136781692505 '''
九、协程实现TC服务端的并发
服务端
from gevent import monkey;monkey.patch_all() # 固定编写 用于检测所有的IO操作(猴子补丁) 不用格式化代码 from gevent import spawn import socket def get_sock(sock): while True: data = sock.recv(1024) print(data.decode('utf8')) sock.send(data.upper()) def get_server(): severe = socket.socket() severe.bind(('127.0.0.1', 8080)) severe.listen(5) while True: sock, address = severe.accept() spawn(get_sock, sock) s1 = spawn(get_server) s1.join() # 协程的原因所以只要一个线程就能实现并发
客户端
import socket from threading import Thread, current_thread client = socket.socket() client.connect(('127.0.0.1', 8080)) def task(): while True: client.send(('线程%s' % current_thread().name).encode('utf8')) data = client.recv(1024) print(data.decode('utf8')) if __name__ == '__main__': for i in range(100): # 一次性开一百个线程朝服务端请求服务 t = Thread(target=task) t.start()