Python3-协程&异步IO

1.概念:

同步IO:

       CPU的速度远远快于磁盘、网络等 IO,一旦遇到 IO 操 作,如读写文件、发送网络数据时,就需要等待 IO 操作完成,才能继 续进行下一步操作。这种情况称为同步 IO。

 IO操作时当前线程被挂起,其他需要CPU执行的代码无法执行。

 为实现并发必须使用多线程/多进程或进行异步IO,

多线程/进程缺点如下:

  1. 系统不能无上限的增加线程(换句话说,缺点就是受系统资源限制),
  2. 系统切换开销随线程数增加而上升,切换占用时间增加,cpu执行时间减少,任务完成效率下降

 异步IO:

“当代码需要执行一个耗时的 IO 操作时,它只发出 IO 指令,并不等待 IO 结果,然后就去执行其他代码 了。一段时间后,当 IO 返回结果时,再通知CPU进行处理。”

2. 实现

异步 IO 模型需要一个消息循环,在消息循环中,主线程不断地重复“读 取消息-处理消息”这一过程:

1 loop = get_event_loop() 
2 while True:
3     event = loop.get_event()
4     process_event(event)
  • 当遇到 IO 操作时,代码只负责发出 IO 请求,不等待 IO 结果,然后直接结束 本轮消息处理,进入下一轮消息处理过程。当 IO 操作完成后,将收到 一条“IO完成”的消息,处理该消息时就可以直接获取 IO 操作结果。
  • 在“发出 IO 请求”到收到“IO完成”的这段时间里,

同步 IO 模型下,主线 程只能挂起,但异步 IO 模型下,主线程并没有休息,而是在消息循环 中继续处理其他消息。这

异步 IO 模型下,一个线程就可以同时 处理多个 IO 请求,并且没有切换线程的操作。对于大多数 IO 密集型的 应用程序,使用异步 IO 将大大提升系统的多任务处理能力。

3.协程/微线程/纤程 Coroutine

  子程序

  又称子函数,层级调用栈实现,一个线程执行一个子程序。一次调用,一次返回,调用顺序明确。

  协程

  类似子程序,区别如下:

  1. 协程执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行
  2. 由一个线程执行,不是线程切换
  3. 不需要多线程锁机制(因为只有一个线程,也不存在 同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好 了,所以执行效率比多线程高很多)
 1 #Eg:
 2 def A():
 3     print('1') 
 4     print('2')
 5     print('3')
 6 def B():
 7     print('x') 
 8     print('y') 
 9     print('z')
10 
11 #协程执行可能结果:在执行A的过程中,可以随时中断,去执行 B,B也 可能在执行过程中中断再去执行A
12 1 2 x y 3 z

  协程与多核CPU:多进程+协程

实现(Python)generator

EG:生产者消费者

 1 '''
 2 generator实现
 3     注意到 consumer函数是一个 generator,把一个 consumer传入 produce后:
 4         1. 首先调用 c.send(None)启动生成器;
 5         2. 然后,一旦生产了东西,通过 c.send(n)切换到 consumer执行;
 6         3. consumer 通过 yield拿到消息,处理,又通过 yield 把结果传回;
 7         4. produce 拿到 consumer 处理的结果,继续生产下一条消息;
 8         5. produce 决定不生产了,通过 c.close()关闭 consumer,整个过程结束。
 9 由一个线程执行,produce 和 consumer 协作完成任务, 所以称为“协程”,而非线程的抢占式多任务
10 '''
11 
12 def consumer():
13     r = ''
14     while True:
15         n = yield r
16         if not n:
17             return
18         print('[CONSUMER] Consuming %s...' % n)
19         r = '200 OK'
20 def produce(c):
21     c.send(None)
22     n = 0
23     while n < 5:
24         n = n + 1
25         print('[PRODUCER] Producing %s...' % n)
26         r = c.send(n)
27         print('[PRODUCER] Consumer return: %s' % r)
28     c.close()
29 
30 c = consumer()
31 produce(c)

4.asyncio

asyncio 是 Python 3.4 版本引入的标准库,直接内置了对异步 IO 的支持。

asyncio 的编程模型就是一个消息循环。

我们从 asyncio模块中直接获取 一个 EventLoop 的引用,然后把需要执行的协程扔到 EventLoop 中执行, 就实现了异步 IO。
EG:Hello World

 1 '''asyncio
 2 
 3 #用 asyncio 实现 Hello world 代码如下:
 4 
 5 [email protected] 把一个 generator 标记为 coroutine 类型,
 6 2.将这个coroutine放到EventLoop 中执行。
 7 
 8 具体执行流程:
 9 hello()会首先打印出 Hello world!,
10 yield from语法调用另一个 generator。
11 由于 asyncio.sleep()也是一个 coroutine, 所以线程不会等待 asyncio.sleep(),而是直接中断并执行下一个消息循环。
12 当 asyncio.sleep()返回时,线程就可以从 yield from 拿到返回值(此处是 None),然后接着执行下一行语句。
13 '''
14 import asyncio
15 import threading
16 @asyncio.coroutine
17 def hello():
18      print("Hello world!")
19      # 异步调用 asyncio.sleep(1):
20      r = yield from asyncio.sleep(1)
21      print("Hello again!")
22 
23  # 获取 EventLoop:
24  loop = asyncio.get_event_loop()
25  # 执行 coroutine
26  loop.run_until_complete(hello())
27  loop.close()
28 
29   
 1 '''asyncio
 2 #另一个例子:用 asyncio的异步网络连接来获取 sina、sohu 和 163 的网站首页
 3 '''
 4 @asyncio.coroutine
 5 def wget(host):
 6     print('wget %s...' % host)
 7     connect = asyncio.open_connection(host, 80)
 8     reader, writer = yield from connect
 9     header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host
10     writer.write(header.encode('utf-8'))
11     yield from writer.drain()
12     while True:
13         line = yield from reader.readline()
14         if line == b'\r\n':
15             break
16         print('%s header > %s' % (host, line.decode('utf-8').rstrip()))
17         # Ignore the body, close the socket
18         writer.close()
19 
20 loop = asyncio.get_event_loop()
21 tasks = [wget(host) for host in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']]
22 loop.run_until_complete(asyncio.wait(tasks))
23 loop.close()

简化标识异步IO:async 和 await 是针对 coroutine 的新语法
    1. 把@asyncio.coroutine 替换为 async;
    2. 把 yield from替换为 await。 

 1 '''
 2 简化版
 3 '''
 4 import asyncio
 5 @asyncio.coroutine
 6 def hello():
 7     print("Hello world!")
 8     r = yield from asyncio.sleep(1)
 9     print("Hello again!")
10 
11 async def hello():
12     print("Hello world!")
13     r = await asyncio.sleep(1)
14     print("Hello again!")
15 # 获取 EventLoop:
16 loop = asyncio.get_event_loop()
17 # 执行 coroutine
18 loop.run_until_complete(hello())
19 loop.close()
 1 #练习:
 2 
 3 async def wget(host):
 4     print('wget %s...' % host)
 5     connect = asyncio.open_connection(host, 80)
 6     reader, writer = await connect
 7     header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host
 8     writer.write(header.encode('utf-8'))
 9     await writer.drain()
10     while True:
11         line = await reader.readline()
12         if line == b'\r\n':
13             break
14         print('%s header > %s' % (host, line.decode('utf-8').rstrip()))
15         # Ignore the body, close the socket
16         writer.close()
17 
18 loop = asyncio.get_event_loop()
19 tasks = [wget(host) for host in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']]
20 loop.run_until_complete(asyncio.wait(tasks))
21 loop.close()

5.aiohttp

asyncio 可以实现单线程并发 IO操作。如果仅用在客户端,发挥的威力 不大。如果把 asyncio 用在服务器端,例如Web 服务器,由于HTTP 连 接就是 IO 操作,因此可以用单线程+coroutine 实现多用户的高并发支 持。
asyncio 实现了TCP、UDP、SSL 等协议,aiohttp 则是基于 asyncio 实 现的HTTP 框架。

 1 '''
 2 asyncio 可以实现单线程并发 IO操作。
 3 如果仅用在客户端,发挥的威力 不大。
 4 如果把 asyncio 用在服务器端,例如Web 服务器,由于HTTP 连 接就是 IO 操作,
 5 因此可以用单线程+coroutine 实现多用户的高并发支持
 6 asyncio 实现了TCP、UDP、SSL 等协议,aiohttp 则是基于 asyncio 实 现的HTTP 框架
 7 Eg:编写HTTP服务器,分别处理URL
 8     / - 首页返回 b'<h1>Index</h1>';
 9     /hello/{name} - 根据URL参数返回文本 hello, %s!。
10 '''
11 
12 import asyncio
13 from aiohttp import web
14 
15 async def index(request):#参数,aiohttp.web.request实例,包含了所有浏览器发送过来的 HTTP 协议里面的信息,一般不用自己构造
16     await asyncio.sleep(0.5)
17     return web.Response(body=b'<h1>Index</h1>', headers={'content-type':'text/html'})
18 #此处若不指明headers,访问网站会跳出二进制文件的下载
19 #Header 中Content-Type用text/plain,支持将数据以纯文本形式进行编码。浏览器在获取到这种文件时会自动调用html的解析器对文件进行相应的处理。
20 #aiohttp.web.response实例,由web.Response(body='')构造,继承自StreamResponse,功能为构造一个HTTP响应
21 #类声明 class aiohttp.web.Response(*, status=200, headers=None, content_type=None, body=None, text=None)
22 async def hello(request):
23     await asyncio.sleep(0.5)
24     text = '<h1>hello, %s!</h1>' % request.match_info['name']
25     return web.Response(body=text.encode('utf-8'),headers={'content-type':'text/html'})
26 
27 async def init(loop):#loop为传入函数的协程
28     #app = web.Application(loop=loop), 根据报错去掉参数
29     app = web.Application()#创建Web服务器app,处理url,http信息
30     app.router.add_route('GET', '/', index)#将处理函数注册进router
31     app.router.add_route('GET', '/hello/{name}', hello)# 将处理函数对应的URL(HTTP方法metho,URL路径path)绑定,浏览器敲击URL时返回处理函数的内容
32 
33 #下段代码为修改版, 报错提示app.make_handler()被弃用,使用web.AppRunner(app)替换,使用方法如下
34     runner=web.AppRunner(app)
35     await runner.setup()
36     site = web.TCPSite(runner,'localhost',8000)
37     await site.start()
38     print('Server started at http://127.0.0.1:8000...')
39 
40     #初:
41     #使用协程创建监听服务,返回一个创建好的,绑定IP、端口、HTTP协议簇的监听服务的协程
42     #srv = await loop.create_server(app.make_handler(), '127.0.0.1',8000)
43     # return srv
44 
45 loop = asyncio.get_event_loop()#创建协程
46 loop.run_until_complete(init(loop))#初始化协程,运行协程知道完成
47 loop.run_forever()#运行协程直到调用stop

猜你喜欢

转载自www.cnblogs.com/jpga/p/12566663.html
今日推荐