Python使用asyncio标准库对异步IO的支持

 更新时间:2023年11月04日 15:19:51   投稿:yin  
Python中,所有程序的执行都是单线程的,但可同时执行多个任务,不同的任务被时间循环(Event Loop)控制及调度,Asyncio是Python并发编程的一种实现方式;是Python 3.4版本引入的标准库,直接内置了对异步IO的支持

Python中,所有程序的执行都是单线程的,但可同时执行多个任务,不同的任务被时间循环(Event Loop)控制及调度,Asyncio是Python并发编程的一种实现方式;是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。

异步IO

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

在IO操作的过程中,当前线程被挂起,而其他需要CPU执行的代码就无法被当前线程执行了。

因为一个IO操作就阻塞了当前线程,导致其他代码无法执行,所以我们必须使用多线程或者多进程来并发执行代码,为多个用户服务。每个用户都会分配一个线程,如果遇到IO导致线程被挂起,其他用户的线程不受影响。

多线程和多进程的模型虽然解决了并发问题,但是系统不能无上限地增加线程。由于系统切换线程的开销也很大,所以,一旦线程数量过多,CPU的时间就花在线程切换上了,真正运行代码的时间就少了,结果导致性能严重下降。

由于我们要解决的问题是CPU高速执行能力和IO设备的龟速严重不匹配,多线程和多进程只是解决这一问题的一种方法。

另一种解决IO问题的方法是异步IO。当代码需要执行一个耗时的IO操作时,它只发出IO指令,并不等待IO结果,然后就去执行其他代码了。一段时间后,当IO返回结果时,再通知CPU进行处理。

asyncio

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

asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。

asyncio实现Hello world代码如下:

import asyncio

@asyncio.coroutine
def hello():
    print("Hello world!")
    # 异步调用asyncio.sleep(1):
    r = yield from asyncio.sleep(1)
    print("Hello again!")

# 获取EventLoop:
loop = asyncio.get_event_loop()
# 执行coroutine
loop.run_until_complete(hello())
loop.close()

@asyncio.coroutine把一个generator标记为coroutine类型,然后,我们就把这个coroutine扔到EventLoop中执行。

hello()会首先打印出Hello world!,然后,yield from语法可以让我们方便地调用另一个generator。由于asyncio.sleep()也是一个coroutine,所以线程不会等待asyncio.sleep(),而是直接中断并执行下一个消息循环。当asyncio.sleep()返回时,线程就可以从yield from拿到返回值(此处是None),然后接着执行下一行语句。

asyncio.sleep(1)看成是一个耗时1秒的IO操作,在此期间,主线程并未等待,而是去执行EventLoop中其他可以执行的coroutine了,因此可以实现并发执行。

我们用Task封装两个coroutine试试:

import threading
import asyncio

@asyncio.coroutine
def hello():
    print('Hello world! (%s)' % threading.currentThread())
    yield from asyncio.sleep(1)
    print('Hello again! (%s)' % threading.currentThread())

loop = asyncio.get_event_loop()
tasks = [hello(), hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

观察执行过程:

Hello world! (<_MainThread(MainThread, started 140735195337472)>)
Hello world! (<_MainThread(MainThread, started 140735195337472)>)
(暂停约1秒)
Hello again! (<_MainThread(MainThread, started 140735195337472)>)
Hello again! (<_MainThread(MainThread, started 140735195337472)>)

由打印的当前线程名称可以看出,两个coroutine是由同一个线程并发执行的。

如果把asyncio.sleep()换成真正的IO操作,则多个coroutine就可以由一个线程并发执行。

我们用asyncio的异步网络连接来获取sina、sohu和163的网站首页:

import asyncio

@asyncio.coroutine
def wget(host):
    print('wget %s...' % host)
    connect = asyncio.open_connection(host, 80)
    reader, writer = yield from connect
    header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host
    writer.write(header.encode('utf-8'))
    yield from writer.drain()
    while True:
        line = yield from reader.readline()
        if line == b'\r\n':
            break
        print('%s header > %s' % (host, line.decode('utf-8').rstrip()))
    # Ignore the body, close the socket
    writer.close()

loop = asyncio.get_event_loop()
tasks = [wget(host) for host in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

执行结果如下:

wget www.sohu.com...
wget www.sina.com.cn...
wget www.163.com...
(等待一段时间)
(打印出sohu的header)
www.sohu.com header > HTTP/1.1 200 OK
www.sohu.com header > Content-Type: text/html
...
(打印出sina的header)
www.sina.com.cn header > HTTP/1.1 200 OK
www.sina.com.cn header > Date: Wed, 20 May 2015 04:56:33 GMT
...
(打印出163的header)
www.163.com header > HTTP/1.0 302 Moved Temporarily
www.163.com header > Server: Cdn Cache Server V2.0
...

可见3个连接由一个线程通过coroutine并发完成。

小结

asyncio提供了完善的异步IO支持;异步操作需要在coroutine中通过yield from完成;多个coroutine可以封装成一组Task然后并发执行。

到此这篇关于Python使用asyncio标准库对异步IO的支持的文章就介绍到这了,更多相关Python使用asyncio内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • python中for用来遍历range函数的方法

    python中for用来遍历range函数的方法

    今天小编就为大家分享一篇python中for用来遍历range函数的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-06-06
  • Python matplotlib的使用并自定义colormap的方法

    Python matplotlib的使用并自定义colormap的方法

    今天小编就为大家分享一篇Python matplotlib的使用并自定义colormap的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-12-12
  • Python创建文件夹与文件的快捷方法

    Python创建文件夹与文件的快捷方法

    这篇文章主要给大家介绍了关于Python创建文件夹与文件的快捷方法以及批量创建文件夹的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • python执行get提交的方法

    python执行get提交的方法

    这篇文章主要介绍了python执行get提交的方法,实例分析了Python通过get传递数据的技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-04-04
  • pip安装python库的方法总结

    pip安装python库的方法总结

    在本篇文章里小编给大家分享了关于使用pip安装python库的几种常用方法,有需要的朋友们可以参考下。
    2019-08-08
  • 解决django-xadmin列表页filter关联对象搜索问题

    解决django-xadmin列表页filter关联对象搜索问题

    今天小编就为大家分享一篇解决django-xadmin列表页filter关联对象搜索问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-11-11
  • Python进阶篇之正则表达式常用语法总结

    Python进阶篇之正则表达式常用语法总结

    正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配。本文为大家总结了一些正则表达式常用语法,希望有所帮助
    2022-08-08
  • Python的高阶函数用法实例分析

    Python的高阶函数用法实例分析

    这篇文章主要介绍了Python的高阶函数用法,结合实例形式分析了Python针对字符串、列表转换、运算、过滤等操作函数相关使用技巧与注意事项,需要的朋友可以参考下
    2019-04-04
  • Python3实现的回文数判断及罗马数字转整数算法示例

    Python3实现的回文数判断及罗马数字转整数算法示例

    这篇文章主要介绍了Python3实现的回文数判断及罗马数字转整数算法,涉及Python数值运算、转换等相关操作技巧,需要的朋友可以参考下
    2019-03-03
  • Python基于回溯法解决01背包问题实例

    Python基于回溯法解决01背包问题实例

    这篇文章主要介绍了Python基于回溯法解决01背包问题,结合实例形式分析了Python回溯法采用深度优先策略搜索解决01背包问题的相关操作技巧,需要的朋友可以参考下
    2017-12-12

最新评论