协程的同步异步,数据传递,asyncio模块。
协程概念:
协程又称为为线程,纤程,是一种用户态的轻量级线程
发展历史:
1.最初的生成器变形 yield/send
2.引入@aysnico.coroutine和yield from
3.在最近的python3.5版本中引入了async/await关键字
理解协程:
1.普通理解:线程是系统级别的,他们是由操作系统调度。协程是程序级别的,他是由程序员根据需求自己调度,
我们把一些线程中的一个个函数称为子程序。那么子程序在执行的过程中可以中断去执行别的子程序,别的子程序也可以
中断回来继续执行之前的子程序,只就是协程。也就是说同一线程下的一段断码1执行着可以终端,然后去执行别的函数,
然后再回来执行代码块1,从中断的地方开始执行
2.专业理解:协程拥有自己的寄存器,上下文和栈,协程在调度切换时,将寄存器上下文和栈保存到其他
的地方,在切回来时,恢复先前保存的寄存器上下文和栈。因此,协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。
优点:
1、无需线程上下文切换的开销,(不需要调度时间)协程避免了无意义的调度,由此提高了性能,但是程序员那必须
自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力
2、无需原子操作锁定及同步的开销
3、方便控制流,简化编程模型
4、高并发+高扩展性+低成本,一个CPU支持上万个协程不是问题。
缺点:
1、无法调度多核资源,协程的本质是一个线程,它不能同时将单个CPU的多个核使用,协程需要和进程配合使用
才能运行在多CPU上,但是有一般不需要,除非是CPU密集型的应用。
2、进行阻塞操作(耗时IO)会阻塞整个程序。
子程序:
在所有的语言中都是层级调度,比如A中调用B,B在执行过程中调用C,C执行完返回,B执行完返回,
最后是A执行完毕,是通过栈实现的,一个线程就是执行一个子程序,子程序的调用总是有一个入口,一次返回,调用
的顺序是明确的。
协程的数据传递:
.send
python 由于GIL(全局锁)的存在,不能发挥多核的优势,其性能一直被人诟病,在IO密集型的网络
编程里,异步处理比同步处理会提成上千倍。
同步:
只完成事物的路基,先执行第一个事物,如果阻塞了会一直等待,知道这个事物完成,在执行
第二个事物,顺序执行。
异步:
是和同步相对的,指在处理调用这个事物的之后,不会等待这个事物的处理结果,会直接会处
理结果,直接切处理第二个事物了,通过状态,通知,回调来通知调用者处理结果。
#同步代码
import time
def func():
time.sleep(1)
for i in range(5):
func()
print(time.time())
#异步代码
import asyncio
async def func1():
#模拟一个io操作
asyncio.sleep(1)
print(time.time(),"+++++++++++++")
loop = asyncio.get_event_loop()
for i in range(5):
loop.run_until_complete(func1())
asyncio模块:
python 3.4版本引入的标准库,直接内置了对异步IO的支持
编程模式:
是有一个消息循环,我们从asyncio模式中直接换区一个EcentLoop的引用,然后把需要
执行的协程扔到EventLoop中执行,就实现了异步。
说明:
到目前为止实现协程的不仅只有asyncio,还有gevent 和tornado实现类似的功能
关键字说明:
1、event_loop 事件循环:程序开启一个无线循环,把一些函数注册到事件循环中,当满足条件
发生时,调用相应的协程函数。
2、coroutine 协程:协程对象,指一个使用了async关键字定义的函数,它的调用不会立即执行
函数,而是会返回一个协程对象,协程对象需要注册到事件循环中,由事件循环调用。
3、task 任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步的封装,其中
包含了任务的各种状态,
4、future :代表将来执行或没有执行的任务的结果,它和task没有本质上区别,
5、async/await:python3.5开始用于定义协程的关键字,async用于挂起阻塞异步调用接口,
#通过async生成协程对象,协程不能直接运行,需要将协程加入到事件循环中
async def run(x):
print("waiting:%d"%x)
#得到一个协程对象,这个时候run()函数没有执行。
coroutine = run(2)
#引用一个时间循环(注意:真实情况实在asyncio模块中获取一个引用)
loop = asyncio.get_event_loop()
#协程对象加入到事件循环中,协程对象不能直接运行,在注册事件循环的时候,其实就是run_until_complete
#方法对协程对象包装哼了一个任务对象,task对象是Future类的子类,保存了协程运行后的状态,用于获取协程的结果
loop.run_until_complete(coroutine)
task 创建任务:
task = asyncio.ensure_future(coroutine)
#task = loop.create_task(coroutine)
loop.run_until_complete(task)
#future
#定义一个回调函数,参数为future,任务对象
并发和并行
并发:指多个任务需要同时进行,
并行:同一时刻有多个任务执行
并发类似一个老师在同一时间段辅导不同的人功课,
并行类似好几个老师同时辅导不同的人功课。
不同线程的事件循环
一般情况我们的事件循环用于注册协程,有一些协程需要动态的添加到事件循环中,简单的方式就是使用多线程,当前线程创建一个事件循环,然后开启一个新线程,在新线程中启动事件循环.当前线程不会被block
import asyncio
import threading
import time
def start_loop(lp):
asyncio.set_event_loop(lp)
lp.run_forever()
async def run(x):
print("x:%d"%x)
await asyncio.sleep(x)#模拟另一个协程
print("finsish %d"%x)
start = time.time()
loop = asyncio.get_event_loop()
#创建新线程,用来启动事件循环,此时不会阻塞主线程
threading.Thread(target=start_loop, args=(loop,)).start()
end = time.time()
print("time:",end-start)
#给事件循环添加任务
# loop.call_soon_threadsafe(run, 4)
# loop.call_soon_threadsafe(run, 6)
asyncio.run_coroutine_threadsafe(run(4),loop)
asyncio.run_coroutine_threadsafe(run(6),loop)#这样就同步了.
#获取网页信息的协程练习.
#www.163.com www.baidu.com www.sohu.com www.sina.com.cn
import asyncio
import time
import threading
def start_loop(lp):
asyncio.set_event_loop(lp)
lp.run_forever()
async def weget(url):
print("加载:",url)
connect = asyncio.open_connection(url,443)
reader,writer = await connect #挂起
#连接成功
header= "GET / HTTP/1.0\r\nHost: %s\r\n\r\n"%url
writer.write(header.encode("utf-8"))
await writer.drain()
loop = asyncio.get_event_loop()
#创建新线程,用来启动事件循环,此时不会阻塞主线程
threading.Thread(target=start_loop, args=(loop,)).start()
#给事件循环添加任务
for url in ["www.163.com", "www.baidu.com", "www.sohu.com", "www.sina.com.cn"]:
asyncio.run_coroutine_threadsafe(weget(url), loop)