一.同步异步
线程的三种状态:
1.就绪
2.运行
3.阻塞
阻塞 遇到了IO操作 代码卡住 无法执行 CPU会切换到其他任务
非阻塞 与阻塞相反 代码正在执行(运行状态)或处于就绪状态
阻塞和非阻塞秒回的是运行的状态
同步:提交任务必须等待任务完成,才能执行下一行
异步:提交任务不需要等待任务完成,立即执行下一行
指的是一种提交任务方式
def task(): for I in range(10000): I+=1000 print("111111") print("start") task()#同步提交 print("en") form threading import Thread print("start1") Thread(target=task).start()#异步提交 print("end")
二.异步回调
为什么需要异步回调
子进程帮助主进程完成任务 处理任务的结果应该还给主进程
其他方式也可以将数据交给主进程
1.shutdown 主进程会叨叨所有任务完成
2.result函数 会阻塞知道任务完成
注意:
回调函数什么时候执行?子进程任务完成时
谁在执行回调函数?主进程
线程的异步回调
使用方法相同 唯一不同是线程没有主线程,都是子线程执行
三.生产者消费者
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor from threading import current_thread pool = ThreadPoolExecutor() import requests def get_data_task(url): print(current_thread(),"正在生产数据!|") response =requests.get(url) text = response.content.decode("utf-8") print(text) return text def parser_data(f): print(current_thread(),"处理数据") print("正在解析:长度%s"%len(f.result())) urls=[ "http://www.baidu.con", "http://www.taobao.con", "http://www.jindong.con", "http://www.4399.con" ] if __name__=='__main__': for url in urls: f = pool.submit(get_data_task,url) f.add_done_callback(parser_data) print("over")
四.线程队列
import queue # 普通队列 先进先出 q = queue.Queue() q.put("a") q.put("b") print(q.get()) print(q.get()) # 堆栈队列 先进后出 后进先出 函数调用就是进栈 函数结束就出栈 递归造成栈溢出 q2 = queue.LifoQueue() q2.put("a") q2.put("b") print(q2.get()) # 优先级队列 q3 = queue.PriorityQueue() # 数值越小优先级越高 优先级相同时 比较大小 小的先取 q3.put((-100,"c")) q3.put((1,"a")) q3.put((100,"b")) print(q3.get())
五.协程
协程的目的是在单线程下实现并发
为什么出现协程? 因为cpython 由于GIL 导致同一时间只有一个线程再跑
意味着 如果你的程序时计算密集 多线程效率也不会提升
如果是io密集型 有没有必要再单线程下实现并发
没有 我会开启多线程来处理io 子线遇到io cpu切走 但是请问 你能保证一定切到主线吗? 不能保证
有 如果可以 我在遇到io的时候转而去做计算 这样一来可以保证cpu一直在处理你的程序 当然时间太长也要切走
总结一下:单线下实现并发 将io阻塞时间用于执行计算 可以提高效率 原理:一直使用CPU直到超时
怎么实现单线程并发?
并发 指的是 看起来像是同时运行 实际是在任务间来回切换 同时需要保存执行的状态
任务一堆代码 可以用函数装起来
1.如何让两个函数切换执行
yield可以保存函数的执行状态
通过生成器可以实现伪并发
并发不一定提升效率 反而会降低效率 当任务全是计算时
2.如何知道发生了io? 从而切换执行
目前咱们实现不了
第三方模块 greenlet 可以实现并发 但是不能检测io
第三方模块 gevent 封装greenlet 可以实现单线程并发 并且能够检测io操作 自动切换
协程的应用场景:
TCP 多客户端实现方式
1.来一个客户端就来一个进程 资源消耗较大
2.来一个客户端就来一个线程 也不能无限开
3.用进程池 或 线程池 还是一个线程或进程只能维护一个连接
4.协程 一个线程就可以处理多个客户端 遇到io就切到另一个
六 greenlet
如果我们在单个线程内有20个任务,要想实现在多个任务之间切换,使用yield生成器的方式过于麻烦(需要先得到初始化一次的生成器,然后再调用send。。。非常麻烦),而使用greenlet模块可以非常简单地实现这20个任务直接的切换
from greenlet import greenlet def eat(name): print('%s eat 1' %name) g2.switch('egon') print('%s eat 2' %name) g2.switch() def play(name): print('%s play 1' %name) g1.switch() print('%s play 2' %name) g1=greenlet(eat) g2=greenlet(play) g1.switch('egon')#可以在第一次switch时传入参数,以后都不需要
单纯的切换(在没有io的情况下或者没有重复开辟内存空间的操作),反而会降低程序的执行速度
greenlet只是提供了一种比generator更加便捷的切换方式,当切到一个任务执行时如果遇到io,那就原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题。
单线程里的这20个任务的代码通常会既有计算操作又有阻塞操作,我们完全可以在执行任务1时遇到阻塞,就利用阻塞的时间去执行任务2。。。。如此,才能提高效率,这就用到了Gevent模块。
七 gevent
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
g1=gevent.spawn(func,1,,2,3,x=4,y=5)创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的 g2=gevent.spawn(func2) g1.join() #等待g1结束 g2.join() #等待g2结束 #或者上述两步合作一步:gevent.joinall([g1,g2]) g1.value#拿到func1的返回值
遇到IO阻塞时会自动切换任务
import gevent def eat(name): print('%s eat 1' %name) gevent.sleep(2) print('%s eat 2' %name) def play(name): print('%s play 1' %name) gevent.sleep(1) print('%s play 2' %name) g1=gevent.spawn(eat,'egon') g2=gevent.spawn(play,name='egon') g1.join() g2.join() #或者gevent.joinall([g1,g2]) print('主')
上例gevent.sleep(2)模拟的是gevent可以识别的io阻塞,
而time.sleep(2)或其他的阻塞,gevent是不能直接识别的需要用下面一行代码,打补丁,就可以识别了
from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面,如time,socket模块之前
或者我们干脆记忆成:要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头
from gevent import monkey;monkey.patch_all()
import gevent
import time
def eat():
print('eat food 1')
time.sleep(2)
print('eat food 2')
def play():
print('play 1')
time.sleep(1)
print('play 2')
g1=gevent.spawn(eat)
g2=gevent.spawn(play_phone)
gevent.joinall([g1,g2])
print('主')
什么是协程
协程是是单线程下的并发,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。
为什么需要协程
因为线程是从内核级别进行调度,当某个线程遇到IO或执行时间过长时,操作系统会强制切换到其他线程
而在单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率
协程的使用场景
TCP 多客户端实现方式
1.来一个客户端就来一个进程 资源消耗较大
2.来一个客户端就来一个线程 也不能无限开
3.用进程池 或 线程池 还是一个线程或进程只能维护一个连接
4.协程 一个线程就可以处理多个客户端 遇到io就切到另一个
2.使用协程完成TCP套接字编程 支持多客户端同时访问
3.什么是异步 什么是异步回调 为什么需要异步回调
异步 :提交任务不需要等待任务完成,立即执行下一行
子进程帮助主进程完成任务 处理任务额结果应该还给主进程
提高效率,让每一个线程都工做起来.