1.线程回调
回调: 在线程池/进程池 每次提交任务 都会返回一个表示任务的对象 Future对象 Future对象具备一个绑定方法 add_done_callback 用于指定回调函数 add 意味着可以添加多个回调函数 如果直接使用Thread的话 如何完成回调: from threading import Thread import time # res = None def call_back(res): print("任务结果拿到了:%s" % res) def parser(res): print("任务结果拿到了:%s" % res) def task(callback): # global res print("run") time.sleep(1) # # return 100 res = 100 # 表示任务结果 callback(res) # 执行回调函数 并传入任务结果 t = Thread(target=task,args=(parser,)) t.start() # t.join() # print(res) print("over")
2.线程中的队列
from queue import Queue,LifoQueue,PriorityQueue 1.Queue() 先进先出 # 与进程中的Joinablequeue 使用方式一模一样 但是 不具备IPC # q = Queue() # # q.put("123") # q.put("456") # # print(q.get()) # print(q.get()) # # # print(q.get(block=True,timeout=3)) # q.task_done() # q.task_done() # q.join() # print("over") 2.LifoQueue last in first out 后进先出 先进 后出 模拟堆栈 # 除顺序以外别的都一样 # lq = LifoQueue() # # lq.put("123") # lq.put("456") # # print(lq.get()) # print(lq.get()) 3. PriorityQueue 具备优先级的队列 # 可以存储一个可以比较大小的对象 比较越小的优先级越高
自定义对象 不能使用比较运算符 所以不能存储 q=queue.PriorityQueue() #put进入一个元组,元组的第一个元素是优先级(通常是数字,也可
以是非数字之间的比较),数字越小优先级越高 q.put((20,'a')) q.put((10,'b')) q.put((30,'c')) print(q.get()) print(q.get()) print(q.get()) ''' 结果(数字越小优先级越高,优先级高的优先出队): (10, 'b') (20, 'a') (30, 'c')
3.事件EVENT
事件Event: 事件,表示发生了某件事情,我们可以去关注某个事件然后采取一
些行动 本质上事件是用来线程间通讯的 ,用于状态同步 from threading import Thread,Event import time boot_event = Event() # boot_event.clear() 回复事件的状态为False # boot_event.is_set() 返回事件的状态 # boot_event.wait()等待事件发生 ,就是等待事件被设置
为True # boot_event.set() 设置事件为True def boot_server(): print("正在启动服务器......") time.sleep(3) print("服务器启动成功!") boot_event.set() # 标记事件已经发生了 def connect_server(): boot_event.wait() # 等待事件发生 print("链接服务器成功!") t1 = Thread(target=boot_server) t1.start() t2 = Thread(target=connect_server) t2.start()
4.协程
协程的目的就是要在单线程中实现并发 并发: 多个任务看起来是同时运行 本质是切换+保存状态 1.生成器中yiled实现并发效果 # 使用生成器来实现 单线 并发多个任务 import time # def func1(): # a = 1 # for i in range(10000000): # a += 1 # # print("a run") # yield # # def func2(): # res = func1() # a = 1 # for i in range(10000000): # a += 1 # # print("b run") # next(res) # # st = time.time() # func2() # print(time.time() - st) def func1(): a = 1 for i in range(10000000): a += 1 def func2(): a = 1 for i in range(10000000): a += 1 st = time.time() func1() func2() print(time.time() - st) 经过测试 单线程并发并不能提高性能 , 对于计算密集型
任务而言 对于IO操作而言 必须具备能够检测io操作 并自动切换其他
区任务 ,这才提高效率
2.greenlet greenlet需要手动切换,而且不能 检测IO ,是对yie
ld的封装 import greenlet import time def task1(): print("task1 run") g2.switch() print("task1 over") g2.switch() def task2(): print("task2 run") g1.switch() time.sleep(2) print("task2 over") g1 = greenlet.greenlet(task1) g2 = greenlet.greenlet(task2) g1.switch() # g2.switch() print("主 over")
3.gevent
gevent 是协程 ,为轻量级线程 ,也称之为微线程,
是应用程序级别的任务调度方式
提高效率:
在Cpython中有GIL锁 导致多线程不能并行执行丧失了 多
核优势,即使开启了多线程也只能并发, 这时候完全 可以
使用协程来实现并发
协程的优缺点:
优点:不会占用更多无用的资源
缺点:如果是计算任务 使用协程反而降低效率
IO密集型任务 采用协程
5.猴子补丁
本质就是把原本阻塞的代码 悄悄换成非阻塞代码 例如 Queue.get(block=false) 当执行get而取不到值时 会抛出异常 只需捕获异常 然后
在发生时切换到其他任务 ,就可以实现遇到IO 切换任务 # gevent 不具备检测IO的能力 需要为它打补丁 打上补
丁之后就能检测IO # 注意补丁一定打在最上面 必须保证导入模块前就打好补丁 from gevent import monkey monkey.patch_all() # from threading import current_thread import gevent,time def task1(): print(current_thread(),1) print("task1 run") # gevent.sleep(3) time.sleep(3) print("task1 over") def task2(): print(current_thread(),2) print("task2 run") print("task2 over") # spawn 用于创建一个协程任务 g1 = gevent.spawn(task1) g2 = gevent.spawn(task2) # 任务要执行,必须保证主线程没挂 因为所有协程任务
都是主线在执行 ,必须调用join来等待协程任务 # g1.join() # g2.join() # 理论上等待执行时间最长的任务就行 , 但是不清楚
谁的时间长 可以全部join gevent.joinall([g1,g2]) print("over")
协程: 在Cpython 多线程无法并行 执行 ,在IO密集任务中
并且并发量较大,无法开启更多的线程时,造成后续的任
务无法处理,即使前面都在等待IO 就可以使用单线程 实现并发,当某一个任务处于IO阻塞时
,可以切换到其他的任务来执行,可以充分利用CPU时间片
,如果任务量足够,可以占用CPU直到超时 最终的解决方案:多进程 +单线程 + 协程 对于更多的任务: 1.集群 所有服务器干的活都一样 2.分布式 每个服务器就干某个活