40 day of python
线程
守护线程
在主线程结束(包括所有的子线程)之后守护线程才结束
面向对象开启线程
原本写在func中的代码挪到run方法中
同步的机制
锁
GIL锁 是全局解释器锁 线程没有并行
是Cpython解释器的
在开发python解释器的时候可以减少很多细粒度的锁
为什么有了GIL锁还会产生数据安全问题呢?
因为GIL锁的是线程 而不是具体的内存
互斥锁 不能连续acquire两次
递归锁 可以在用一个线程/进程中被acquire多次
死锁 : 是一种现象,而不是一个工具
为什么产生死锁 : 代码的逻辑有问题
如何解决死锁 :
如果在服务阶段 ---> 递归锁 ---> 排查逻辑 ---> 互斥锁
如果在测试阶段 ---> ---> 排查逻辑 ---> 互斥锁
信号量 锁+计数器
和池的区别:信号量是有几个任务起几个进程/线程 池是固定的线程/进程数,不限量的任务
现象:信号量慢 且耗资源 池快
事件 Event wait set clear is_set
条件 condition wait notify release
定时器 Timer
join 同步控制 用来获取结果
锁 数据安全
池 提高效率,解决并发
今日内容
线程
队列
线程池
协程
IO模型
from multiprocessing import Queue,JoinableQueue # 进程IPC队列 from queue import Queue # 线程队列 先进先出 from queue import LifoQueue # 后进后出的 put get put_nowait get_nowait full empty qsize 队列Queue 先进先出 自带锁 数据安全 栈 LifoQueue 后进后出 自带锁 数据安全
from multiprocessing import Queue,JoinableQueue from queue import Queue from queue import LifoQueue lq = LifoQueue(4) lq.put(123) lq.put(456) lq.put('abc') lq.put('abc') # lq.put('abc') # lq.put('abc') # lq.put('abc') print(lq) print(lq.get()) print(lq.get()) print(lq.get()) print(lq.get()) from queue import PriorityQueue # 优先级队列 pq = PriorityQueue() pq.put((10,'aaa')) pq.put((5,'bbb')) pq.put((100,'ccc')) pq.put((20,'ddd')) print(pq.get()) print(pq.get()) print(pq.get()) print(pq.get())
Threading 没有线程池的
Multiprocessing pool
concurrent.futures帮助你管理线程池和进程池
import time from threading import currentThread,get_ident from concurrent.futures import ThreadPoolExecutor # 帮助你启动线程池的类 from concurrent.futures import ProcessPoolExecutor # 帮助你启动进程池的类 def func(i): time.sleep(1) print('in %s %s'%(i,currentThread())) return i**2 def back(fn): print(fn.result(),currentThread()) # map启动多线程任务 t = ThreadPoolExecutor(5) t,map(func,range(20)) for i in range(20): t.submit(func,i) # submit异步提交任务 t = ThreadPoolExecutor(5) for i in range(20): t.submit(func,i) t.shutdown() print('main : ',currentThread()) # 起多少个线程池 # 5*cpu的个数 # 获取任务结果 t = ThreadPoolExecutor(20) ret_l = [] for i in range(20): ret = t.submit(func,i) ret_l.append(ret) t.shutdown() for ret in ret_l: print(ret.result()) print('main : ',currentThread())
回调函数
from threading import currentThread,get_ident from concurrent.futures import ProcessPoolExecutor def func(i): time.sleep(1) print('in %s %s'%(i,currentThread())) return i**2 def back(fn): print(fn.result(),currentThread()) if __name__ == '__main__': t = ProcessPoolExecutor(20) for i in range(100): t.submit(func,i).add_done_callback(back)
回调函数(进程版)
import os import time from concurrent.futures import ProcessPoolExecutor # 帮助你启动线程池的类 def func(i): time.sleep(1) print('in %s %s'%(i,os.getpid())) return i**2 def back(fn): print(fn.result(),os.getpid()) if __name__ == '__main__': print('main : ',os.getpid()) t = ProcessPoolExecutor(20) for i in range(100): t.submit(func,i).add_done_callback(back)
multiprocessing模块自带进程池的
threading模块是没有进程池的
concurrent.futures 进程池 和 线程池
高度封装
进程池/线程池的统一的使用方式
创建线程池/进程池 ProcessPoolExecutor ThreadPoolExecutor
ret = t.submit(func,arg1,arg2....) 异步提交任务
ret.result() 获取结果,如果要想实现异步效果,应该使用列表
map(func,iterable)
shutdown close+join 同步控制的
add_done_callback 回调函数,在回调函数内接收的参数是一个对象,需要通过result来获取返回值
回调函数仍然在主进程中执行
进程:资源分配的最小单位,班级
线程:CPU调度最小单位,人
什么是协程?一个人分八半儿
CPython线程不能利用多核的
多线程凭什么能够做到并发???
多个线程无法利用多核
一个线程
能同时执行多个任务
协程:能在一条线程的基础上,在多个任务之间互相切换
节省了线程开启的消耗
是从python代码的级别调度的
正常的线程是CPU调度的最小单位
协程的调度并不是由操作系统来完成的
def func(): print(1) x = yield 'aaa' print(x) yield 'bbb' g = func() print(next(g)) print(g.send('****'))
在多个函数之间互相切换的功能 - 协程
def consumer(): while True: x = yield print(x) def producer(): g = consumer() next(g) # 预激 for i in range(10): g.send(i) producer()
yeild 只能程序之间的切换,没有重利用任何IO操作的时间
程序执行的上下文切换的工具
greenlet 程序上下文切换的
import time from greenlet import greenlet # 协程模块 # 单纯的程序切换耗费时间 def eat(): print('吃') time.sleep(1) g2.switch() # 切换 print('吃完了') time.sleep(1) g2.switch() def play(): print('玩儿') time.sleep(1) g1.switch() print('玩儿美了') time.sleep(1) g1 = greenlet(eat) g2 = greenlet(play) g1.switch() # 切换
遇到IO就切换
gevent pip3 install gevent
greenlet是gevent的底层
gevent是基于greenlet实现的
python代码在控制程序的切换
使用协程减少IO操作带来的时间消耗
from gevent import monkey;monkey.patch_all() import gevent import time def eat(): print('吃') time.sleep(2) print('吃完了') def play(): print('玩儿') time.sleep(1) print('玩儿美了') g1 = gevent.spawn(eat) g2 = gevent.spawn(play) gevent.joinall([g1,g2])
g1.join()
g2.join()
没执行
为什么没执行???是需要开启么?
没有开启但是切换了
gevent帮你做了切换,做切换是有条件的,遇到IO才切换
gevent不认识除了gevent这个模块内以外的IO操作
使用join可以一直阻塞直到协程任务完成
帮助gevent来认识其他模块中的阻塞
from gevent import monkey;monkey.patch_all()写在其他模块导入之前
协程完成的socket
4c并发5000 qps
5个进程
20个线程
500个协程
协程 能够在单核的情况下 极大地提高CPU的利用率
不存在数据不安全
也不存在线程切换\创造的时间开销
切换是用户级别的,程序不会因为协程中某一个任务进入阻塞状态而使整条线程阻塞
线程的切换
时间片到了 降低CPU的效率
IO会切 提高CPU效率
from gevent import monkey;monkey.patch_all() import socket import gevent def talk(conn): while True: conn.send(b'hello') print(conn.recv(1024)) sk = socket.socket() sk.bind(('127.0.0.1',9090)) sk.listen() while True: conn,addr = sk.accept() gevent.spawn(talk,conn)
import socket from threading import Thread def client(): sk = socket.socket() sk.connect(('127.0.0.1',9090)) while True: print(sk.recv(1024)) sk.send(b'bye') for i in range(500): Thread(target=client).start()
from threading import Condition acquire release wait 阻塞 notify 让wait解除阻塞的工具 wait还是notify在执行这两个方法的前后 必须执行acquire和release from threading import Condition,Thread def func(con,i): con.acquire() # 判断某条件 con.wait() print('threading : ',i) con.release() con = Condition() for i in range(20): Thread(target=func,args=(con,i)).start() con.acquire() # 帮助wait的子线程处理某个数据直到满足条件 con.notify_all() con.release() while True: num = int(input('num >>>')) con.acquire() con.notify(num) con.release()