#Python3中 asyncio 原生协程

asyncio 原生协程

asyncio协程的作用

asyncio 是干什么的?
异步网络操作
高并发
协程

  1. 包含各种特定系统实现的模块化事件循环

  2. 传输和协议抽象

  3. 对tcp,upd,ssl,子进程,延时调用以及其他的具体支持

  4. 模仿futures模块但适用于事件循环使用的Future类

  5. 基于yield from的协议和任务,可以让你用顺序的方式编写并发代码

  6. 必须使用一个将产生阻塞io的调动时,有接口可以把这个事件转移到线程池

  7. 模仿threading模块中的同步原语,可以在单线程协程内调用

  8. 总结: asyncio库是一个python实现了原生协程异步等功能的库,并且可以让我们使用顺序的方式编程,不用考虑事件循环以及事件循环的变量传递问题。他是Python解决异步io编程的一整套解决方案

asyncio的基础使用

使用的方式:
事件循环+驱动协程+io多路复用

event_loop 事件循环:程序开启一个无限的循环,程序员会把一些函数注册到事件循环上。
当满足事件发生的时候,调用相应的协程函数。

coroutine 协程:协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,
而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。

task  任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含任务的各种状态。

future: 代表将来执行或没有执行的任务的结果。它和task上没有本质的区别

async/await 关键字:python3.5 用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。

@asyncio.coroutine
装饰器@asyncio.coroutine

把一个generator(生成器)标记为coroutine(协程)类型,然后,
就把这个coroutine(协程)扔到eventloop(事件循环)中去执行。

yield from可以让我们方便的调用另一个generator(生成器),
由于asyncio.sleep(1)也是一个coroutine(协程)

所以线程不会等待asyncio.sleep而是直接中断并执行下一个消息循环,当asyncio.sleep返回的时候,
线程就在yield from拿到返回值,此处是None

然后执行下一个语句

把asyncio.sleep(1)看成是一个耗时1秒的IO操作。在此期间主线程没有等待,
而是去执行eventloop其他可以执行的coroutine因此可以实现并发执行
  1. 基本使用方式
    1. 协程使用async定义
    2. await 后面跟上io操作
    3. async 中不能写入阻塞io操作
    4. async/await 关键字:python3.5用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口
promise 对象有三种状态:成功(Fulfilled)失败(Rejected)等待(Pending)
promise 不配合 async await 时,使用 .then() .catch() 处理成功和失败情况是目前的常规方案。
async 表示这是一个async函数,await只能用在这个函数里面。async 对象也是一个 promise 对象。
await 表示在这里等待promise返回结果了,再继续执行。
await 后面跟着的应该是一个promise对象(当然,其他返回值也没关系,不过那样就没有意义了…)
很多库的接口返回 promise 对象,await 后赋值给一个变量后使用其 resolve 的值。
[例如](http://mongoosejs.com/docs/api.html#query_Query-exec)
注意三点,promise 对象的状态,promise 对象上的方法(then,catch),promise 对象返回的值。
promise 是当时为了解决回调地狱的解决方案,也是当前处理异步操作最流行和广泛使用的方案,
async 和 await 最为当前的终极方案两只之间还有一些过渡方案。

yield from 用法
带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,

	
	import asyncio  	# 导入异步协程库
	import time  		# 导入时间库
	
	
	@asyncio.coroutine 	 # 将get_html()即生成器(generator)装饰为协程类型
	def get_html(url):
	    while 1:
	        start_time = time.time()
	        print(start_time)
	        print('start l', url)
	        yield from asyncio.sleep(5)  	# 由于asyncio.sleep(2)也是一个coroutine(协程)
	        print('end 2', url)  	# 所以线程不会等待asyncio.sleep而是直接中断并执行下一个消息循环
	        end_time = time.time()  	# 当asyncio.sleep返回的时候,再去执行终端后未完成的语句
	        print(end_time)
	        print("-----------")
	
	
	if __name__ == '__main__':
	    loop = asyncio.get_event_loop()			# 获取EventLoop事件
	    tasks = [get_html("asd"), get_html("asd")]   #创建两个协程事件
	    loop.run_until_complete(asyncio.wait(tasks))		#运行事件直到结束,asyncio.wait执行这个协程
	    
	#(输出)
	1543936141.3279934
	start l asd
	1543936141.3289928
	start l asd
	end 2 asd
	1543936146.3291001
	-----------
	1543936146.3291001
	start l asd
	end 2 asd
	1543936146.3291001
	-----------
	1543936146.3291001
	start l asd
	end 2 asd
	1543936151.329194
	-----------
	1543936151.3296947
	start l asd
	end 2 asd
	1543936151.3296947
	-----------
	中断

>原生协程用法
	
	import asyncio		#导入异步协程库
	import time			#导入时间库
	
	#协程中是不能使用同步阻塞编程的
	async def get_html(url):
	    print('start get url',url)
	    await asyncio.sleep(2)
	    print('end get url')
	
	if __name__ == '__main__':
	    start_time = time.time()
	    #事件循环,代替自己写的loop
	    loop = asyncio.get_event_loop()
	    loop.run_until_complete(get_html("www.baidu.com"))
	    print('times',time.time()-start_time)         #计算时间
	    
	#(输出)
	start get url www.baidu.com
	end get url
	times 2.0032832622528076
  1. 多个任务提交

    1. 把任务完批量提交给asyncio进行完成
    2. 为什么不能用sleep,当loop循环到sleep的时候,如果使用await的时候,就会直接向下执行,不会阻塞,但是如果使用time.sleep的话,那么程序会阻塞,等到sleep完成后再向下执行。
    
    import asyncio
    import time
    
    
    @asyncio.coroutine
    def get_html(times):
        print('start get url'.format(times))
        yield from asyncio.sleep(times)
        print('end get url {
           }'.format(times))
    
    
    if __name__ == '__main__':
        start_time = time.time()
        loop = asyncio.get_event_loop()
        tasks = list()
        for i in range(1, 10):
            tasks.append(get_html(i))
    
        loop.run_until_complete(asyncio.wait(tasks))
    
    import asyncio
    import time
    #协程中是不能使用同步阻塞编程的
    async def get_html(url):
        print('start get url',url)
        await asyncio.sleep(2)
        print('end get url')
    
    if __name__ == '__main__':
        start_time = time.time()
        #事件循环,代替自己写的loop
        loop = asyncio.get_event_loop()
        tasks = [get_html('www.baidu.com') for i in range(10)]
        loop.run_until_complete(asyncio.wait(tasks))
        print('times',time.time()-start_time)
    
    
    

    简单进行一个爬虫

#coding:utf-8
import time,asyncio,aiohttp
				#aiohttp则是基于asyncio实现的HTTP框架,底层使用了io多路复用。
num = 0

async def fetch(session,url):
    async with session.get(url) as response:	  #session.get(url)获取页面
        return await response.text()		#返回页面解码后的信息

async def main(url,semaphore):
    async with semaphore:		#这里进行执行asyncio.Semaphore
        async with aiohttp.ClientSession() as session:  创建了一个命名为session的aiohttp.ClientSession 对象
            html = await fetch(session, url)		#相当于 yield from 
            global num
            num += 1
            print(html)
            print('------------{
     }'.format(num))


async def run():
    semaphore = asyncio.Semaphore(500)		 # 限制并发量为500,这里windows需要进行并发限制,
    						#不然将报错:ValueError: too many file descriptors in select()

    to_get = [main("http://blog.jobbole.com/{
     }/".format(_),semaphore) for _ in range(114364,120000)] #总共1000任务
    await asyncio.wait(to_get)
    
    #Python中对于无需关注其实际含义的变量可以用_代替,这就和for i in range(5)一样,因为这里我们对i并不关心,所以用_代替仅获取值而已。


if __name__ == '__main__':
#    now=lambda :time.time()
    loop = asyncio.get_event_loop()
    loop.run_until_complete(run())
    loop.close()

ValueError: too many file descriptors in select()的原因
因为asyncio内部用到了select,而select就是那个什么系统打开文件数是有限度的,上面的代码一次性将处理url的函数作为任务扔进了一个超大的List中,这就引起了错误,用这种形式无法写大规模爬虫。
解决:如上面的代码。回调一下,设置并发量, semaphore = asyncio.Semaphore(500) #Windows在500以下。

  1. 获取async返回值

    1. 使用 asyncio.ensure_future(get_html('www.baidu.com'))futures类来给出结果的期望在循环调用完成后获取返回结果

    2. 使用 loop.create_task(get_html('www.baidu.com'))直接添加到循环中来表示想获取到结果

    3. 使用 add_done_callback(call_back)可以添加回调函数,通知一个函数表示这个函数已经完成

    
    import asyncio
    import time
    
    
    # 协程中是不能使用同步阻塞编程的
    async def get_html(url):
        print('start get url', url)
        await asyncio.sleep(2)
        print('end get url')
        return 'get_futures'
    
    
    # 函数必须要使用到future,就是完成好的函数
    def call_back(future):
        print('tasks compete')
    
    
    if __name__ == '__main__':
        start_time = time.time()
        # 事件循环,代替自己写的loop
        loop = asyncio.get_event_loop()
        # 1.使用future,获取future期望,传递给loop
        # get_future = asyncio.ensure_future(get_html('www.baidu.com'))
        get_future = loop.create_task(get_html('www.baidu.com'))
        # 把结果返回给callback函数
        get_future.add_done_callback(call_back)
        loop.run_until_complete(get_future)
        print('times', time.time() - start_time)
        print(get_future.result())
    
    
  2. wait 和 gather

    1. wait和gather都表示等待任务完成

    2. gather可以接受过个任务组来执行

      	
      import asyncio
      import time
      #协程中是不能使用同步阻塞编程的
      async def get_html(url):
          print('start get url',url)
          await asyncio.sleep(2)
          print('end get url')
      
      if __name__ == '__main__':
          start_time = time.time()
          #事件循环,代替自己写的loop
          loop = asyncio.get_event_loop()
          tasks = [get_html('www.baidu.com') for i in range(10)]
          tasks2 = [get_html('www.google.com') for i in range(10)]
          # loop.run_until_complete(asyncio.wait(tasks))
          loop.run_until_complete(asyncio.gather(*tasks,*tasks2))
          print('times',time.time()-start_time)
      
      
      

      或者使用

      import asyncio
      import time
      #协程中是不能使用同步阻塞编程的
      async def get_html(url):
          print('start get url',url)
          await asyncio.sleep(2)
          print('end get url')
      
      async def get_html2(url):
          print('start get url',url)
          await asyncio.sleep(4)
          print('end get url')
      
      if __name__ == '__main__':
          start_time = time.time()
          #事件循环,代替自己写的loop
          loop = asyncio.get_event_loop()
          t1 = [get_html('www.baidu.com') for i in range(10)]
          t2 = [get_html2('www.google.com') for i in range(10)]
          tasks = asyncio.gather(*t1)
          tasks2 = asyncio.gather(*t2)
          # loop.run_until_complete(asyncio.wait(tasks))
          loop.run_until_complete(asyncio.gather(tasks,tasks2))
      
          print('times',time.time()-start_time)
      
      
      
  3. 取消协程

    1. 可以使用cancel去取消协程
    
    import asyncio
    import time
    #协程中是不能使用同步阻塞编程的
    async def get_html(url):
        print('start get url',url)
        await asyncio.sleep(2)
        print('end get url')
    
    async def get_html2(url):
        print('start get url',url)
        await asyncio.sleep(4)
        print('end get url')
    
    if __name__ == '__main__':
        start_time = time.time()
        #事件循环,代替自己写的loop
        loop = asyncio.get_event_loop()
        t1 = [get_html('www.baidu.com') for i in range(10)]
        t2 = [get_html2('www.google.com') for i in range(10)]
        tasks = asyncio.gather(*t1)
        tasks2 = asyncio.gather(*t2)
        # loop.run_until_complete(asyncio.wait(tasks)
        try:
            loop.run_until_complete(asyncio.gather(tasks,tasks2))
        except KeyboardInterrupt:
            print(tasks2.cancel())
            loop.close()
            
    
    
        print('times',time.time()-start_time)
    
    

    或者使用

    
    import asyncio
    import time
    #协程中是不能使用同步阻塞编程的
    async def get_html(url):
        print('start get url',url)
        await asyncio.sleep(2)
        print('end get url')
    
    async def get_html2(url):
        print('start get url',url)
        await asyncio.sleep(4)
        print('end get url')
    
    if __name__ == '__main__':
        start_time = time.time()
        #事件循环,代替自己写的loop
        loop = asyncio.get_event_loop()
        t1 = [get_html('www.baidu.com') for i in range(10)]
        t2 = [get_html2('www.google.com') for i in range(10)]
        # tasks = asyncio.gather(*t1)
        # tasks2 = asyncio.gather(*t2)
        # loop.run_until_complete(asyncio.wait(tasks)
        try:
            loop.run_until_complete(asyncio.wait(t1))
        except KeyboardInterrupt:
            for task in asyncio.Task.all_tasks():
                print(task.cancel())
            loop.stop()
    
    
    
    
        print('times',time.time()-start_time)
    
    
  4. stop,close的区别

    1. stop是等待事件循环里面的所有协程执行完成之后停止

    2. close 是会清空所有队列和线程池等。

    
    import asyncio
    import time
    def call_back(x):
        time.sleep(3)
        print('get',x)
    
    def stoploop(loop):
        loop.close()
    
    if __name__ == "__main__":
        loop = asyncio.get_event_loop()
    
        loop.call_soon(call_back,32)
        loop.call_soon(stoploop,loop)
        loop.run_forever()
    
    

call_soon call_later

  1. call_soon和停止循环

    解释:
    1. call_soon不会立刻执行,而是等待loop的最新一次循环取执行
    2. 我们可以调用loop.stop()去停止协程的事件循环

    
    import asyncio
    import time
    def call_back(x):
        time.sleep(3)
        print('get',x)
    
    def stoploop(loop):
        loop.stop()
    
    if __name__ == "__main__":
        loop = asyncio.get_event_loop()
    
        loop.call_soon(call_back,32)
        loop.call_soon(stoploop,loop)
        loop.run_forever()
    
    
  2. call_later会让程序延迟(秒)执行

    
    import asyncio
    import time
    def call_back(x):
        time.sleep(3)
        print('get',x)
    
    def stoploop(loop):
        loop.stop()
        time.sleep(4)
        loop.close()
    
    if __name__ == "__main__":
        loop = asyncio.get_event_loop()
    
        loop.call_later(3,call_back,32)
        loop.call_later(2, call_back, 31)
        # loop.call_later(2,stoploop,loop)
        loop.run_forever()
    
    
  3. call_at loop的执行时间戳,loop自己的运行时间戳

    import asyncio
    import time
    def call_back(x):
        time.sleep(3)
        print('get',x)
    
    def stoploop(loop):
        loop.stop()
        time.sleep(4)
        loop.close()
    
    if __name__ == "__main__":
        loop = asyncio.get_event_loop()
        now = loop.time()
        loop.call_at(now+3,call_back,32)
        loop.call_at(now+2, call_back, 31)
        loop.run_forever()
    
    
  4. loop.call_soon_threadsafe() 线程安全

    说明: 如果是线程的调用,可以保护线程中的变量的安全

线程池+asyncio

说明: 使用asyncio的前提是库本身支持,就像http请求,库是没有支持的,如果我们想使用httprequest 那么只好使用多线程,以至于整个主线程不阻塞

	import asyncio
	from concurrent.futures import ThreadPoolExecutor
	import time
	def call_back(x):
	    time.sleep(3)
	    print('get',x)
	
	def stoploop(loop):
	    loop.stop()
	    time.sleep(4)
	    loop.close()
	
	if __name__ == "__main__":
	    loop = asyncio.get_event_loop()
	    ex = ThreadPoolExecutor(max_workers=10)
	    tasks = list()
	    for i in range(20):
	        tasks.append(loop.run_in_executor(ex,call_back,3))
	
	    loop.run_until_complete(asyncio.wait(tasks))

asyncio http请求(使用tcp)

async自带的有tcp协议,我们不用自己去注册事件,直接使用即可

  1. 一次性返回所有结果

    
    import socket
    # 使用tcp连接
    import asyncio
    
    
    async def get_html():
        # 返回读写
        reader, writer = await asyncio.open_connection('www.baidu.com', 80)
    
        data = "GET {
           } HTTP/1.1\r\nHost:{
           }\r\nConnection:close\r\nUser-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36\r\n\r\n".format(
            '/moist-master/rimi_linux_mysql/blob/master/tcp_ip_socket/notes/http_proto.md', 'www.baidu.com').encode('utf8')
        # 发送数据
        writer.write(data)
        print('sended')
    
        datas = []
        # for 循环不会阻塞
        async for data in reader:
            datas.append(data.decode('utf8'))
    
        res = "".join(datas)
    
        return res
    
    
    if __name__ == '__main__':
        loop = asyncio.get_event_loop()
        tasks = []
        for i in range(10):
            tasks.append(asyncio.ensure_future(get_html()))
    
        loop.run_until_complete(asyncio.wait(tasks))
    
        for i in tasks:
            print(i.result())
    
    
    
    
  2. 逐个返回结果

    
    import socket
    # 使用tcp连接
    import asyncio
    
    
    async def get_html():
        # 返回读写
        reader, writer = await asyncio.open_connection('www.baidu.com', 80)
    
        data = "GET {
           } HTTP/1.1\r\nHost:{
           }\r\nConnection:close\r\nUser-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36\r\n\r\n".format(
            '/moist-master/rimi_linux_mysql/blob/master/tcp_ip_socket/notes/http_proto.md', 'www.baidu.com').encode('utf8')
        # 发送数据
        writer.write(data)
        print('sended')
    
        datas = []
        # for 循环不会阻塞
        async for data in reader:
            datas.append(data.decode('utf8'))
    
        res = "".join(datas)
    
        return res
    
    async def start():
        tasks = []
        for i in range(10):
            tasks.append(asyncio.ensure_future(get_html()))
    
        for task in asyncio.as_completed(tasks):
            res = await task
            print(res)
    
    
    
    if __name__ == '__main__':
        loop = asyncio.get_event_loop()
        loop.run_until_complete(start())
    
    

你可能感兴趣的:(Python,Python3中,asyncio,原生协程,asyncio,协程,协程基本用法)