昨日内容回顾 TCP服务端实现并发 1.将不同的功能尽量拆分成不同的函数 拆分出来的功能可以被多个地方使用 1.将连接循环和通信循环拆分成不同的函数 2.将通信循环做成多线程 GIL(全局解释器锁) 在CPython解释器才有GIL的概念,不是python的特点 GIL也是一把互斥锁 将并发变成串行 牺牲了效率但是提高了数据的安全 ps: 1.针对不同的数据 应该使用不同的锁去处理 2.自己不要轻易的处理锁的问题 哪怕你知道acquire和release 当业务逻辑稍微复杂的一点情况下 极容易造成死锁 CPython中的GIL的存在是因为python的内存管理不是线程安全的 内存管理 引用计数:值与变量的绑定关系的个数 标记清除:当内存快要满的时候 会自动停止程序的运行 检测所有的变量与值的绑定关系 给没有绑定关系的值打上标记,最后一次性清除 分代回收:(垃圾回收机制也是需要消耗资源的,而正常一个程序的运行内部会使用到很多变量与值 并且有一部分类似于常量,减少垃圾回收消耗的时间,应该对变量与值的绑定关系做一个分类 ) 新生代(5S)》》》青春代(10s)》》》老年代(20s) 垃圾回收机制扫描一定次数发现关系还在,会将该对关系移至下一代 随着代数的递增 扫描频率是降低的 同一个进程下的多个线程能否同时运行 GIL类似于是加在解释器上面的一把锁 死锁与递归锁 递归锁 可以被第一个抢到该锁的人多次的acquire和release,内部会有一个计数 acquire就是加 release就是减 当别人在抢这把锁的时候 只要计数不为零 永远也别想抢到 信号量 如果互斥锁是单个卫生间 那么信号量就是多个卫生间 event事件 e = Event() e.wait() 等待信号 e.set() 发送信号 线程q 队列 Queue 堆栈 LifoQueue 优先级 P...Queue 只有一个参数 是元组的形式 数字越小优先级越高 进程池与线程池 开进程开线程都需要消耗资源,只不过两者比较的情况线程消耗的资源比较少 在计算机能够承受范围之内最大限度的利用计算机 什么是池? 在保证计算机硬件安全的情况下最大限度的利用计算机 池其实是降低了程序的运行效率 但是保证了计算机硬件的安全 (硬件的发展跟不上软件的速度) 线程池进程池 协程 进程:资源单位 线程:执行单位 协程:单线程下实现并发 并发 切换+保存状态 ps:看起来像同时执行的 就可以称之为并发 协程:完全是程序员自己意淫出来的名词 单线程下实现并发 并发的条件? 多道技术 空间上的复用 时间上的复用 切换+保存状态 程序员自己通过代码自己检测程序中的IO 一旦遇到IO自己通过代码切换 给操作系统的感觉是你这个线程没有任何的IO ps:欺骗操作系统 让它误认为你这个程序一直没有IO 从而保证程序在运行态和就绪态来回切换 提升代码的运行效率 切换+保存状态就一定能够提升效率吗??? 当你的任务是iO密集型的情况下 提升效率 如果你的任务是计算密集型的 降低效率 yield 保存上一次的结果 多进程下开多线程 多线程下再开协程 IO模型(了解)
TCP 单线程实现并发
import socket from threading import Thread,current_thread def client(): client = socket.socket() client.connect(('127.0.0.1',8080)) n = 0 while True: data = f'{current_thread().name}{n}' client.send(data.encode('utf-8')) res = client.recv(1024) print(res.decode('utf-8')) n += 1 for i in range(400): t = Thread(target=client) t.start()
from gevent import monkey;monkey.patch_all() import socket from gevent import spawn server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) def talk(conn): while True: try: data = conn.recv(1024) if len(data) == 0:break print(data.decode('utf-8')) conn.send(data.upper()) except ConnectionResetError as e: print(e) break conn.close() def server1(): while True: conn,addr = server.accept() spawn(talk,conn) if __name__ == '__main__': g1 = spawn(server1) g1.join()
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor import time import os pool = ThreadPoolExecutor(5) # # 括号内可以传参数指定线程池内的线程个数, 也可以不传 # 不传,默认是当前所在计算机的cpu个数乘5 # pool = ProcessPoolExecutor() # 默认是当前计算机cpu的个数 """ 池子中创建的进程/线程创建一次就不会再创建了 至始至终用的都是最初的那几个 这样的话节省了反复开辟进程/线程的资源 """ def task(n): print(n,os.getpid()) # 查看当前进程 time.sleep(2) return n ** 2 def call_back(n): print('拿到了异步提交任务的返回结果:',n.result()) """ 提交任务的方式 同步:提交任务之后 原地等待任务的返回结果 期间不做任何事 异步:提交任务之后 不等待任务的返回结果(异步的结果怎么拿???) 直接执行下一行代码 """ # pool .submit(task,1) # 朝线程池中提交任务 异步提交 # print('主') """ 异步回调机制:当异步提交的任务有返回结果之后,会自动触发回调函数的执行 """ if __name__ == '__main__': t_list = [] for i in range(20): res = pool.submit(task,i).add_done_callback(call_back) # 提交任务的时候 绑定一个回调函数 一旦该任务有结果 立刻执行对于的回调函数 # print(res.result()) # 原地等待返回的结果 t_list.append(res) # pool.shutdown() # 关闭池子 等待池子中所有的任务执行完毕之后 才会往下运行代码 for p in t_list: print(">>>:",p.result())
"""""" """串行执行""" import time def func1(): for i in range(10000): i + 1 def func2(): for i in range(10000): i + 1 start = time.time() func1() func2() stop = time.time() print(stop-start) # 0.0009989738464355469 """基于yield 并发执行""" def func3(): while True: 10000000 + 1 yield def func4(): g = func3() for i in range(10000): time.sleep(100) # 模拟IO,yield并不会捕捉到并自动切换 i + 1 next(g) start=time.time() func4() stop=time.time() print(stop-start) """ 需要找到一个能够识别IO的一个工具 gevent模块 """ from gevent import monkey;monkey.patch_all() # # 由于该模块经常被使用 所以建议写成一行 from gevent import spawn import time """ 注意gevent模块没办法自动识别time.sleep等io情况 需要你手动再配置一个参数 """ def heng(): print('哼') time.sleep(2) print('哼') def ha(): print('哈') time.sleep(3) print('哈') def heiheihei(): print('嘿嘿嘿') time.sleep(5) print('嘿嘿嘿') start = time.time() g1 = spawn(heng) g2 = spawn(ha) # spawn会检测所有的任务 g3 = spawn(heiheihei) g1.join() g2.join() g3.join() # heng() # ha() print(time.time() - start)