对于来自JavaScript编码者来说,异步编程不是什么新东西,但对于Python开发者来说,async函数和future(类似JS的promise)可不是那么容易能理解的。

Concurrency vs Parallelism

Concurrency和Parallelism听起来一样,但在实际编程里它们有着较大的不同。

想象下你在做饭的时候写书,看起来好像你在同一时间做两件事情,实际你只是在两项事情中相互切换,当你在等水开的时候你就可以去写书,但你切菜时你要暂停写作。这就叫做concurrency。唯一一种使用parallel做这两项工作的办法就是得有两个人,一个人写作,一个人做饭,这就是多核CPU的工作方式了。

为什么asyncio

异步编程允许你在单个线程中并发执行代码。对比多线程处理方式,该方式由你来决定如何由一个任务切换到另一个任务,tasks之间共享数据也更加容易和简单。

    def queue_push_back(x):
if len(list) < max_size:
list.append(x)

如果我们在多线程执行上面的额代码,有可能第二行代码在同一时间被执行,那么同一时间就有两个元素被加入到列表中,那实际列表长度就会操作max_size

另一个异步编程的好处是内存使用。每次一个新的线程创建,也需要开辟一些新内存用来进行上下文切换。如果使用了异步编程,这在单线程中就不存在该问题。

如何在Python编程async代码

Asyncio包含三个主要组件:coroutine, event loop和future

Coroutine

coroutine是异步函数,通过在函数定义def前使用async关键字。

async def my_task(args):
pass my_coroutine = my_task(args)

我们使用了async关键字定义了一个函数,该函数并没有执行,返回了一个coroutine对象。

有两种从一个coroutine中获取异步函数的结果

第一种使用await关键字,仅只能在async函数中用来等待coroutine结束返回结果

result = await my_task(args)

第二种是将它加入到event loop中,接下来我们做详尽讨论。

Event loop

event loop对象负责执行异步代码以及决定异步函数如何进行切换。在创建了event loop后,我们就可以添加多个coroutines给它,coroutines将会调用了run_until_complete或者run_forever执行。

# create loop
loop = asyncio.new_event_loop()
# add coroutine to the loop
future = loop.create_task(my_coroutine)
# stop the program and execute all coroutine added
# to the loop concurrently
loop.run_until_complete(future)
loop.close()

Future

future类似一个占位对象用来存放异步函数结果,提供函数状态。当coroutine添加到event lop时创建future.有两种方式创建:

future1 = loop.create_task(my_coroutine)
# or
future2 = asyncio.ensure_future(my_coroutine)

第一个方法是增加一个coroutine到loop中,返回一个task,它是future的子类。第二种方法非常类似,它接收一个coroutine,并加入到了默认loop中,唯一的区别是,它也可以接收一个future参数,它将不会做任何事情,直接将futrue返回。

一个简单的程序

import asyncio

async def my_task(args):
pass def main():
loop = asyncio.new_event_loop()
coroutine1 = my_task()
coroutine2 = my_task()
task1 = loop.create_task(coroutine1)
task2 = loop.create_task(coroutine2)
loop.run_until_complete(asycnio.wait([task1, task2]))
print('task1 result:', task1.result())
print('task2 result:', task2.result())
loop.close()

就让如你所看见的,我们在执行异步函数前需要先创一个coroutine,然后我们将创建future/task,把它添加到event loop。到现在病没有如何的异步函数被执行,只有当我们调用loop.run_until_completed,event loop开始执行所有的通过loop.createt_task或者asyncio.ensure_future添加的coroutines。loop.run_until_completed将会阻塞应用程序,仅当所有的future执行完毕后。在本例中,我们使用asyncio.wait()创建future,当传递的所有future执行完后我们就获取到了future所有的结果。

异步函数

有一件事需要注意的是在Python中使用async声明的函数并不意味着函数会并发执行。如果使用一个普通函数,在前面加入async关键字,event loop并不会中断你的函数去执行另一个coroutine。允许event loop进行切换coroutine相当简单,使用await关键字就会允许event loop可以切换其他注册到loop中的coroutine。

import asyncio

async def print_numbers_async1(n, prefix):
for i in range(n):
print(prefix, i) async def print_numbers_async2(n, prefix):
for i in range(n):
print(prefix, i)
if i % 5 == 0:
await asyncio.sleep(0) loop1 = asyncio.new_event_loop()
count1_1 = loop1.create_task(print_numbers_async1(10, 'c1_1'))
count2_1 = loop1.create_task(print_numbers_async1(10, 'c2_1'))
loop1.run_until_complete(asyncio.wait([count1_1, count2_1]))
loop1.close() loop2 = asyncio.new_event_loop()
count1_2 = loop2.create_task(print_numbers_async2(10, 'c1_2'))
count2_2 = loop2.create_task(print_numbers_async2(10, 'c2_2'))
loop2.run_until_complete(asyncio.wait([count1_2, count2_2]))
loop2.close()

如果我们执行该代码,我们可以看到loop1将会在c1_1完全执行完后才去执行c2_1。而在loop2每打印五个数值后就会进行切换。

真实案例

现在我们Python中最进本的异步编程,现在让我们写一个真实例子,我们从互联网下载一系列页面,并打印出开头三行。

import aiohttp
import asyncio async def print_preview(url):
# connect to the server
async with aiohttp.ClientSession() as session:
# create get request
async with session.get(url) as response:
# wait for response
response = await response.text() # print first 3 not empty lines
count = 0
lines = list(filter(lambda x: len(x) > 0, response.split('\n')))
print('-'*80)
for line in lines[:3]:
print(line)
print() def print_all_pages():
pages = [
'http://textfiles.com/adventure/amforever.txt',
'http://textfiles.com/adventure/ballyhoo.txt',
'http://textfiles.com/adventure/bardstale.txt',
] tasks = []
loop = asyncio.new_event_loop()
for page in pages:
tasks.append(loop.create_task(print_preview(page))) loop.run_until_complete(asyncio.wait(tasks))
loop.close() if __name__ == "__main__":
print_all_pages()

这里的代码也很容易理解,我们使用异步函数下载URL,并且打印了前三行。然后我们创建了一个函数用来构建一个页面了列表,交给print_preview去执行,将coroutine加入到loop,把future放到了一个列表中 ,我们执行event loop,在所有coroutine执行完后程序结束。

异步生成器

最后我想谈谈的是异步生成器。要实现一个异步生成器相当简单:

import asyncio
import math
import random async def is_prime(n):
if n < 2:
return True for i in range(2, n):
await asyncio.sleep(0)
if n % i == 0:
return False return True async def prime_generator(n_prime):
counter = 0
n = 0
while counter < n_prime:
n += 1
prime = await is_prime(n)
if prime:
yield n
counter += 1 async def check_email(limit):
for i in range(limit):
if random.random() > 0.8:
print('1 new email')
else:
print('0 new email')
await asyncio.sleep(2) async def print_prime(n):
async for prime in prime_generator(n):
print('new prime number found:', prime) def main():
loop = asyncio.new_event_loop()
prime = loop.create_task(print_prime(3000))
email = loop.create_task(check_email(10))
loop.run_until_complete(asyncio.wait([prime, email]))
loop.close() if __name__ == "__main__":
main()

异常处理

在coroutine内部抛出异常时并不会中断应用程序,如果你没有处理异常的话你将看到类似如下错误:

Task exception was never retrieved

有两个方法来修正,在获取future结果时捕获异常,或者在future调用exception方法.

深入了解

现在你已经了解如何使用asyncio编写并发代码,如果你想深入了解的话,查看官方文档。

在Python中使用asyncio进行异步编程的更多相关文章

  1. Python中使用模块和库编程

    """ python中使用模块和库编程 导入模块 import modulename [as alias] from modulename import fun1,fun ...

  2. C#中委托实现的异步编程

    所谓同步:如果在代码中调用了一个方法,则必须等待该方法所有的代码执行完毕之后,才能回到原来的地方执行下一行代码. 异步:如果不等待调用的方法执行完,就执行下一行代码. 1.0 同步例子: class ...

  3. .NET 中的 async/await 异步编程

    原文出处: Teroy 的博客 前言 最近在学习Web Api框架的时候接触到了async/await,这个特性是.NET 4.5引入的,由于之前对于异步编程不是很了解,所以花费了一些时间学习一下相关 ...

  4. ASP.Net中的async+await异步编程

    在.NET Framework4.5框架.C#5.0语法中,通过async和await两个关键字,引入了一种新的基于任务的异步编程模型(TAP).在这种方式下,可以通过类似同步方式编写异步代码,极大简 ...

  5. python中的多线程和多进程编程

    注意:多线程和多线程编程是不同的!!! 第一点:一个进程相当于一个要执行的程序,它会开启一个主线程,多线程的话就会再开启多个子线程:而多进程的话就是一个进程同时在多个核上进行: 第二点:多线程是一种并 ...

  6. 最新Python异步编程详解

    我们都知道对于I/O相关的程序来说,异步编程可以大幅度的提高系统的吞吐量,因为在某个I/O操作的读写过程中,系统可以先去处理其它的操作(通常是其它的I/O操作),那么Python中是如何实现异步编程的 ...

  7. asyncio异步编程【含视频教程】

    不知道你是否发现,身边聊异步的人越来越多了,比如:FastAPI.Tornado.Sanic.Django 3.aiohttp等. 听说异步如何如何牛逼?性能如何吊炸天....但他到底是咋回事呢? 本 ...

  8. asyncio异步编程

    1. 协程 协程不是计算机提供,程序员认为创造 协程(Coroutine),也可以被称为微线程,是一种用户态内的上下文切换技术,其实就是一个线程实现代码块相互切换执行.例如: def func1(): ...

  9. Python Web学习笔记之多线程编程

    本次给大家介绍Python的多线程编程,标题如下: Python多线程简介 Python多线程之threading模块 Python多线程之Lock线程锁 Python多线程之Python的GIL锁 ...

随机推荐

  1. Git_学习_00_资源帖

    1.廖雪峰: (1)Git教程 2.阮一峰: (1)Git分支管理策略 (2)Git远程操作详解 (3)Git 使用规范流程 (4)Github 的清点对象算法 (5)常用 Git 命令清单 (6)G ...

  2. 1057 Stack (30)(30 分)

    Stack is one of the most fundamental data structures, which is based on the principle of Last In Fir ...

  3. ACM学习历程—Hihocoder 1233 Boxes(bfs)(2015北京网赛)

    hihoCoder挑战赛12 时间限制:1000ms 单点时限:1000ms 内存限制:256MB   描述 There is a strange storehouse in PKU. In this ...

  4. ACM学习历程—HYSBZ 2818 Gcd(欧拉函数 || 莫比乌斯反演)

    Description 给定整数N,求1<=x,y<=N且Gcd(x,y)为素数的数对(x,y)有多少对. Input 一个整数N Output 如题 Sample Input 4 Sam ...

  5. 拓扑排序 POJ 1094 Sorting It All Out

    题意:给定N个字和M行他们之间的关系,要求输出他们的拓扑排序.此题采用边输入边检测的方式,如果发现环,就结束并输出当前行号:如果读取到当前行时,可以确定拓扑序列就输出,不管后面的输入(可能包含环路): ...

  6. TS学习之解构与展开

    一.解构 1.解构数组 let input = [1, 2]; let [first, second] = input; console.log(first); // outputs 1 consol ...

  7. 转载:Android Studio调试功能使用总结

    这段时间一直在使用Intellij IDEA, 今天把调试区工具的使用方法记录于此. 先编译好要调试的程序. 1.设置断点 选定要设置断点的代码行,在行号的区域后面单击鼠标左键即可. 2.开启调试会话 ...

  8. hibernate HQL查询

    hql(都要在事务中完成)session.beginTransaction();session.getTransaction().commit(); session.beginTransaction( ...

  9. oracle中创建sequence指定起始值

    oracle中创建sequence指定起始值 DECLARE V_Area_Id NUMBER; BEGIN SELECT MAX(T.Area_Id)+10 INTO V_Area_Id FROM ...

  10. Java探索之旅(5)——数组

    1.声明数组变量:        double[] array=new double[10];         double array[]=new double[10];       double[ ...