深入浅出Python中的异步编程

来源:互联网 发布:建模数据哪里找 编辑:程序博客网 时间:2024/05/22 14:03

如何使用Python编写异步程序,以及为什么你想要做这样的事情?

 

一个同步程序是我们大多数人开始了写作,并可以被看作是执行一次在一个实施步骤,一个接一个。

即使使用条件分支,循环和函数调用,我们仍然可以考虑一次执行一个执行步骤的代码,完成后再继续执行下一步。


下面是几个可以这样工作的示例程序:

·        批处理程序通常被创建为同步程序:获取一些输入,处理它,创建一些输出。一个逻辑上紧跟着另一个,直到我们创建了所需的输出。除了这些步骤之外,还有其他任何事情都不需要注意。

·        命令行程序通常是一些小的,快速的过程,可以将某些东西“转化”为别的东西。这可以表示为一系列连续执行的程序步骤。

 

一个异步程序的行为不同。一次仍然需要执行一个步骤。然而,不同之处在于系统在继续之前可能不会等待执行步骤完成。

这意味着即使先前的执行步骤(或多个步骤)正在“别处”运行,我们仍然继续执行程序的执行步骤。这也意味着当其中一个执行步骤正在“其他地方”运行完成时,我们的程序代码不知何故必须处理它。

我们为什么要这样写一个程序?简单的答案是帮助我们处理特定类型的编程问题。

这是一个概念性的程序,可能是异步编程的候选人:

 

 

我们来看看一个简单的Web服务器

其基本工作单位与上述批量处理相同; 得到一些输入,处理它,创建输出。写作一个同步程序,这将创建一个工作的Web服务器。

这也将是一个绝对可怕的网络服务器。

为什么?在Web服务器的情况下,一个工作单元(输入,过程,输出)不是唯一的目的。它的真正目的是在同一时间和长时间内处理数百个,也许是数千个工作单元。

我们可以使我们的同步Web服务器更好吗?当然,我们可以优化我们的执行步骤,尽可能快地完成它们。不幸的是,这种方法有非常实际的限制,导致Web服务器不能够快速响应,并且不能处理足够的当前用户。

优化上述方法的真正极限是什么?网络速度,文件IO速度,数据库查询速度,其他连接服务的速度等。这个列表的共同特点是它们都是IO功能。所有这些项目比我们的CPU处理速度慢了许多个数量级。

同步程序中,如果执行步骤启动数据库查询(例如),则CPU在查询返回某些数据之前基本上处于空闲状态,并且可以继续执行下一个执行步骤。

对于面向批处理的程序,这不是优先级,处理IO的结果是目标,并且通常比IO要花费更长的时间。任何优化工作都将集中在处理工作上,而不是IO。

文件,网络和数据库IO都非常快,但仍然比CPU慢。异步编程技术使得我们的程序能够利用相对较慢的IO进程,并且让CPU能够完成其他工作。

当我开始尝试了解异步编程时,我所读过的问题和文档就谈到了编写非阻塞代码的重要性。是的,这也没有帮助我。

什么是非阻塞代码?什么是阻塞代码?这些信息就像有一本参考手册,没有任何关于如何以有意义的方式使用这些技术细节的实际背景。

 

 

现实世界是异步的

编写异步程序是不同的,很难得到你的头脑。这很有趣,因为我们生活的世界以及我们如何与之交互,几乎完全是异步的。

这里有一个很多你可以涉及的例子:作为一个父母试图同时做几件事情; 平衡收支簿,洗衣服,留意孩子。

我们甚至没有考虑到这一点,但是让我们稍微分析一下:

·        平衡收支簿是我们试图完成的一项任务,我们可以将其视为同步任务; 一步接一步,直到完成。

·        但是,我们可以脱离洗衣机,卸下烘干机,将衣物从洗衣机移到烘干机,并在洗衣机中启动另一个负载。但是,这些任务可以异步完成。

·        虽然我们实际上正在使用洗衣机和烘干机,但这是一项同步任务,而且我们正在努力工作,但大部分任务发生在我们启动洗衣机和烘干机之后,随即离开,重新开始收支簿任务。现在任务是异步的,洗衣机和烘干机将独立运行,直到蜂鸣器熄灭,通知我们这个或那个需要注意。

·        看着孩子是另一个异步的任务。一旦他们建立起来,他们就会独立完成,直到他们需要注意。有人饿了,有人受伤了,有人惊呼,作为父母,我们对此作出了反应。孩子们是一项长期的任务,优先考虑任何其他我们可能要做的事情,比如收支簿或洗衣店。

这个例子说明了阻塞和非阻塞的代码。例如,当我们四处移动时,CPU(父母)忙于阻止其他工作。

但没关系,因为CPU很忙,任务也比较快。当我们启动洗衣机和烘干机回去做别的事情时,现在洗衣任务已经变成异步的,因为CPU正在做别的事情,如果你愿意的话改变了上下文,并且在洗衣任务完成时被通知蜂鸣器。

就像人们这是我们的工作方式一样,我们自然而然总是一次又一次地处理多种事情,而且往往没有考虑到这一点。作为程序员的诀窍是如何将这种行为转换成类似的代码。

让我们尝试使用您可能熟悉的代码想法对其进行编程:

 

思考实验#1:“分批”父母

考虑尝试以完全同步的方式完成这些任务。如果我们在这种情况下是一个好父母,我们只是看孩子,等待一些事情发生需要我们的关注。没有别的,像收支簿或洗衣店,在这种情况下会完成。

我们可以按我们想要的方式重新确定任务的优先顺序,但其中只有一个会以同步的方式一次又一次地发生。这将像上面描述的同步Web服务器一样工作,但这将是一个可怕的生活方式。

除了看着孩子们什么都不会做,直到他们睡着了之后,所有其他的任务将在那之后发生,直到深夜。几个星期的这个和大多数父母会跳出窗外。

 

 

思想实验#2:“轮询”父母

让我们改变事情,使用轮询可以完成多件事情。在这种方法中,父母会周期性地脱离当前的任务,并检查是否有其他任务需要关注。

由于我们正在编程一个父母,让我们使我们的轮询间隔大约十五分钟。所以这里每十五分钟一班,家长去检查洗衣机,烘干机或小孩是否需要注意,然后回到工作簿上。如果这些事情中的任何一个都需要注意,那么这个工作就会完成,父母又回到收支簿任务,并继续进行轮询循环。

这工作,任务正在完成,但有一些问题。CPU(家长)花费大量的时间检查不需要关注的事情,因为他们没有完成,就像洗衣机和烘干机。由于轮询时间间隔,任务完成是完全可能的,但一段时间之后,他们不会受到注意,直到十五分钟。而当看到孩子们的任务时,高度重视可能无法容忍15分钟的时间窗口,而没有注意到什么时候会出现严重的错误。

我们可以通过缩短我们的轮询间隔来解决这个问题,但现在CPU在任务之间花费更多的时间上下文切换,并且我们开始降低收益递减点。而且,再过几个星期的生活,看到我之前对窗口和跳跃的评论。

 

 

思想实验#3:“线程”父

作为父母,经常听到“如果我只能克隆自己”。既然我们假装我们可以编程父母,我们可以通过使用线程来做到这一点。

如果我们把所有的任务都看成一个“纲领”,就可以把任务分解为线程,克隆父母这样说话。现在每个任务都有一个父实例。看着孩子们,监控烘干机,监控洗衣机和做收支簿,所有这些都是独立运行的。这听起来像是一个相当不错的解决方案的问题。

但是呢?由于我们必须明确地告诉父实例(CPU)在程序中要做什么,所以我们可能遇到一些问题,因为所有实例都共享程序空间中的所有内容。

例如,监视烘干机的家长看到衣服干燥,控制烘干机并开始卸载。假设在干衣机父母正在卸衣服时,洗衣机的父母看到洗衣机已经完成,控制了洗衣机,然后想要控制干衣机将衣服从洗衣机移到干衣机。当干衣机父母完成卸衣服,父母想要控制洗衣机,并将衣服从洗衣机移到烘干机。

现在这两个父母僵持不下。

两者都控制着自己的资源,并希望控制其他资源。他们将永远等待对方释放控制权。作为程序员,我们必须编写代码来解决这个问题。

这是父线程可能出现的另一个问题。假设不幸的是,一个孩子受到伤害,父母必须把孩子送到紧急护理。这种情况马上就会发生,因为那个父克隆是专门用来看孩子的。但在紧急情况下,家长必须写一个相当大的收支来支付免赔额。

与此同时,在收支簿上工作的家长不知道这个大的收支正在写,突然家庭帐户透支。由于父克隆在同一个程序中工作,并且家庭金钱(收支簿)是该世界中的共享资源,所以我们必须找出让孩子观看父母的方式,以通知收支簿家长发生的事情。或者提供某种锁定机制,以便资源一次只能由一位父母使用,并进行更新。

所有这些东西在程序线程代码中都是可管理的,但是很难正确调试,而且在错误的时候很难调试。

 

 

 

我们来写一些Python代码

现在我们将采取一些在这些“思想实验”中概述的方法,并将它们转化为正常运行的Python程序。

你可以从这个GitHub仓库下载所有的示例代码。

本文中的所有示例都已经使用Python 3.6.1进行了测试,requirements.txt代码示例中包含的文件指出了需要运行所有示例的模块。

我强烈建议设置一个Python虚拟环境来运行代码,以免干扰你的系统Python。

 

 

示例1:同步编程

这个第一个例子显示了一个有点让人费解的方式,让一个任务从一个队列中“工作”并完成这个工作。在这种情况下,工作只是获得一个数字,任务数到这个数字。它还打印每个计数步骤的运行,并在最后打印总计。人为的部分是这个程序为多任务处理队列上的工作提供了一个天真的基础。

"""example_1.py Just a short example showing synchronous running of 'tasks'""" importqueue deftask(name,work_queue):    ifwork_queue.empty():        print(f'Task {name} nothing to do')    else:        whilenotwork_queue.empty():            count=work_queue.get()            total=0            forxinrange(count):                print(f'Task {name} running')                total+=1            print(f'Task {name} total: {total}')  defmain():    """    This is the main entry point for the program    """    # create the queue of 'work'    work_queue=queue.Queue()     # put some 'work' in the queue    forworkin[15,10,5,2]:        work_queue.put(work)     # create some tasks    tasks=[        (task,'One',work_queue),        (task,'Two',work_queue)    ]     # run the tasks    fort,n,qintasks:        t(n,q) if __name__ =='__main__':    main()


这个程序中的“任务”只是一个接受字符串和队列的函数。在执行时,它会查看队列中是否有任何内容要处理,如果是,则从队列中取出值,启动一个for循环来计算该值,并在最后打印总数。它继续,直到队列中没有任何东西,然后退出。

当我们运行这个任务时,我们会得到一个清单,显示任务完成所有工作。其中的循环消耗队列中的所有工作,并执行它。当这个循环退出时,任务二有机会运行,但发现队列是空的,所以它打印一个语句来影响和退出。代码中没有任何东西允许任务1和任务2在一起玩,并在它们之间切换。

 

 

 

示例2:简单合作并发

程序的下一个版本(example_2.py)通过使用生成器增加了两个任务的能力。在任务函数中增加yield语句意味着循环在那一点退出,但是保持它的上下文以便稍后可以重新启动。程序后面的“运行任务”循环在调用时会利用这个优势t.next()。这个声明重新开始任务。

这是一种协同并发的形式。程序正在控制当前的上下文,所以别的东西可以运行。在这种情况下,它允许我们的主要“运行任务”调度程序运行任务函数的两个实例,每个实例都使用同一队列中的工作。这是很聪明的,但是为了得到与第一个程序相同的结果需要很多工作。

"""example_2.py Just a short example demonstrating a simple state machine in Python""" importqueue deftask(name,queue):    whilenotqueue.empty():        count=queue.get()        total=0        forxinrange(count):            print(f'Task {name} running')            total+=1            yield        print(f'Task {name} total: {total}') defmain():    """    This is the main entry point for the program    """    # create the queue of 'work'    work_queue=queue.Queue()     # put some 'work' in the queue    forworkin[15,10,5,2]:        work_queue.put(work)     # create some tasks    tasks=[        task('One',work_queue),        task('Two',work_queue)    ]     # run the tasks    done=False    whilenotdone:        fortintasks:            try:                next(t)            exceptStopIteration:                tasks.remove(t)            iflen(tasks)==0:                done=True  if __name__ =='__main__':    main()


运行此程序时,输出显示任务1和2正在运行,从队列中消耗工作并处理它。这是意图,两个任务正在处理工作,并且每个结束处理队列中的两个项目。但是,再次,相当一部分工作要达到结果。

这里的技巧是使用yield将任务函数转换为生成器的语句来执行“上下文切换”。程序使用这个上下文切换为了运行任务的两个实例。

 

 

 

示例3:阻止呼叫的合作协议

程序的下一个版本(example_3.py)与上一个版本完全一样,除了time.sleep(1)在任务循环体内添加一个调用。这会在任务循环的每个迭代中增加一秒的延迟。添加了延迟以模拟在我们的任务中发生的缓慢IO过程的影响。

我还包括一个简单的ElapsedTime类,以处理报告中使用的开始时间/已用时间功能。

"""example_3.py Just a short example demonstraing a simple state machine in PythonHowever, this one has delays that affect it""" importtimeimportqueuefromlib.elapsed_timeimportET  deftask(name,queue):    whilenotqueue.empty():        count=queue.get()        total=0        et=ET()        forxinrange(count):            print(f'Task {name} running')            time.sleep(1)            total+=1            yield        print(f'Task {name} total: {total}')        print(f'Task {name} total elapsed time: {et():.1f}')  defmain():    """    This is the main entry point for the program    """    # create the queue of 'work'    work_queue=queue.Queue()     # put some 'work' in the queue    forworkin[15,10,5,2]:        work_queue.put(work)      tasks=[        task('One',work_queue),        task('Two',work_queue)    ]    # run the scheduler to run the tasks    et=ET()    done=False    whilenotdone:        fortintasks:            try:                next(t)            exceptStopIteration:                tasks.remove(t)            iflen(tasks)==0:                done=True     print()    print('Total elapsed time: {}'.format(et()))  if __name__ =='__main__':    main()


运行此程序时,输出显示任务1和2正在运行,从队列中消耗工作并像以前一样处理它。随着模拟IO延迟的增加,我们看到我们的协作并发没有得到任何东西,延迟停止了整个程序的处理,而CPU只是等待IO延迟结束。

这正是异步文档中“阻止代码”的含义。注意运行整个程序所花费的时间,这是所有延迟的累计时间。这再次表明,这种方式运行的东西不是一个胜利。

 

 

 

示例4:使用非阻塞调用的协作并发(gevent)

程序的下一个版本(example_4.py)已经被修改了很多。它使用程序顶部的gevent异步编程模块。模块被导入,还有一个叫做模块monkey。

然后monkey调用该模块的一个方法patch_all()。这是在做什么?简单的解释就是设置程序,所以任何其他导入了阻塞(同步)代码的模块都被“修补”,以使其异步。

像大多数简单的解释一样,这不是很有帮助。与我们的示例程序相关的是time.sleep(1)(我们的模拟IO延迟)不再“阻止”程序。相反,它把合作控制权返回给系统。请注意,来自“yield”的声明example_3.py不再存在,现在是time.sleep(1)呼叫的一部分。

所以,如果这个time.sleep(1)功能已经被gevent修补以控制,那么控制权在哪里呢?使用gevent的一个效果就是在程序中启动一个事件循环线程。就我们的目的而言,这就像“运行任务”循环example_3.py。当time.sleep(1)延迟结束时,它将控制权返回到语句后的下一个可执行time.sleep(1)语句。这种行为的优点是CPU不再被延迟阻塞,而是可以自由地执行其他代码。

我们的“运行任务”循环不再存在,而是我们的任务数组包含两个调用gevent.spawn(...)。这两个调用启动两个gevent线程(称为greenlet),它们是上下文切换协作的轻量级微线程,而不是像常规线程那样由于系统切换上下文。

注意gevent.joinall(tasks)我们的任务产生后的权利。这个声明导致我们的程序等待,直到任务一和任务二完成。如果没有这个,我们的计划将继续通过印刷报表,但基本上什么都不做。

"""example_4.py Just a short example demonstrating a simple state machine in PythonHowever, this one has delays that affect it""" importgeventfromgeventimportmonkeymonkey.patch_all() importtimeimportqueuefromlib.elapsed_timeimportET  deftask(name,work_queue):    whilenotwork_queue.empty():        count=work_queue.get()        total=0        et=ET()        forxinrange(count):            print(f'Task {name} running')            time.sleep(1)            total+=1        print(f'Task {name} total: {total}')        print(f'Task {name} total elapsed time: {et():.1f}')  defmain():    """    This is the main entry point for the programWhen    """    # create the queue of 'work'    work_queue=queue.Queue()     # put some 'work' in the queue    forworkin[15,10,5,2]:        work_queue.put(work)     # run the tasks    et=ET()    tasks=[        gevent.spawn(task,'One',work_queue),        gevent.spawn(task,'Two',work_queue)    ]    gevent.joinall(tasks)    print()    print(f'Total elapsed time: {et():.1f}')  if __name__ =='__main__':    main()


运行此程序时,同时注意任务1和任务2,然后等待模拟IO调用。这表明time.sleep(1)呼叫不再阻塞,其他工作正在进行。

在节目结束时注意总时间,这基本上example_3.py是运行时间的一半。现在我们开始看到异步程序的优点了。

能够以非阻塞的方式同时运行IO进程来同时运行两个或更多的事情。通过使用gevent greenlet并控制上下文切换,我们可以在任务之间进行多路复用,而不会有太多麻烦。

 

 

 

示例5:同步(阻止)HTTP下载

程序的下一个版本(example_5.py)向前迈进了一步。现在程序正在用真正的IO做一些实际的工作,使HTTP请求到一个URL列表并获取页面内容,但是它以阻塞(同步)的方式这样做。

我们已经修改了程序,导入了精彩的requests模块来创建实际的HTTP请求,并在队列中添加了一个列表,而不是数字。在任务内部,不是增加一个计数器,而是使用请求模块来获取从队列中获得的URL的内容,并打印要花费多少时间。

"""example_5.py Just a short example demonstrating a simple state machine in PythonThis version is doing actual work, downloading the contents ofURL's it gets from a queue""" importqueueimportrequestsfromlib.elapsed_timeimportET  deftask(name,work_queue):    whilenotwork_queue.empty():        url=work_queue.get()        print(f'Task {name} getting URL: {url}')        et=ET()        requests.get(url)        print(f'Task {name} got URL: {url}')        print(f'Task {name} total elapsed time: {et():.1f}')        yield  defmain():    """    This is the main entry point for the program    """    # create the queue of 'work'    work_queue=queue.Queue()     # put some 'work' in the queue    forurlin[        "http://google.com",        "http://yahoo.com",        "http://linkedin.com",        "http://shutterfly.com",        "http://mypublisher.com",        "http://facebook.com"    ]:        work_queue.put(url)     tasks=[        task('One',work_queue),        task('Two',work_queue)    ]    # run the scheduler to run the tasks    et=ET()    done=False    whilenotdone:        fortintasks:            try:                next(t)            exceptStopIteration:                tasks.remove(t)            iflen(tasks)==0:                done=True     print()    print(f'Total elapsed time: {et():.1f}')  if __name__ =='__main__':    main()


在程序的早期版本中,我们使用a yield来将我们的任务函数转换为一个生成器,并执行上下文切换以便让另一个任务实例运行。

每个任务从工作队列中获取URL,获取URL所指向的页面内容,并报告获取该内容需要多长时间。

和以前一样,yield允许我们的任务运行,但是因为这个程序是同步运行的,所以每次requests.get()调用都会阻塞CPU直到检索到页面。注意在最后运行整个程序的总时间,这对于下一个例子将是有意义的。

 

 

 

示例6:使用gevent的异步(非阻塞)HTTP下载

该版本的程序(example_6.py)修改了以前的版本,以再次使用gevent模块。请记住,gevent monkey.patch_all()调用会修改以下任何模块,以使同步代码变为异步,这包括requests。

现在,任务已经被修改,以移除yield调用,因为requests.get(url)调用不再被阻塞,而是执行上下文切换回gevent事件循环。在“运行任务”部分,我们使用gevent生成任务生成器的两个实例,然后使用joinall()等待它们完成。

"""example_6.py Just a short example demonstrating a simple state machine in PythonThis version is doing actual work, downloading the contents ofURL's it gets from a queue. It's also using gevent to get theURL's in an asynchronous manner.""" importgeventfromgeventimportmonkeymonkey.patch_all() importqueueimportrequestsfromlib.elapsed_timeimportET  deftask(name,work_queue):    whilenotwork_queue.empty():        url=work_queue.get()        print(f'Task {name} getting URL: {url}')        et=ET()        requests.get(url)        print(f'Task {name} got URL: {url}')        print(f'Task {name} total elapsed time: {et():.1f}') defmain():    """    This is the main entry point for the program    """    # create the queue of 'work'    work_queue=queue.Queue()     # put some 'work' in the queue    forurlin[        "http://google.com",        "http://yahoo.com",        "http://linkedin.com",        "http://shutterfly.com",        "http://mypublisher.com",        "http://facebook.com"    ]:        work_queue.put(url)     # run the tasks    et=ET()    tasks=[        gevent.spawn(task,'One',work_queue),        gevent.spawn(task,'Two',work_queue)    ]    gevent.joinall(tasks)    print()    print(f'Total elapsed time: {et():.1f}') if __name__ =='__main__':    main()


在此程序运行结束时,请查看总时间和个别时间以获取URL的内容。你会看到总时间少于所有requests.get()通话的累计时间。

这是因为这些调用是异步运行的,所以我们通过允许一次发出多个请求来有效地利用CPU。

 

 

 

示例7:Twisted的异步(非阻塞)HTTP下载

这个版本的程序(example_7.py)使用Twisted模块来做和gevent模块基本相同的事情,以非阻塞的方式下载URL内容。

Twisted是一个非常强大的系统,并采取一种不同的方法来创建异步程序。在gevent修改模块以使其同步代码异步的地方,Twisted提供了自己的函数和方法来达到相同的目的。

在example_6.py使用修补的requests.get(url)调用来获取URL的内容的地方,我们在这里使用Twisted函数getPage(url)。

在这个版本中,@defer.inlineCallbacks函数装饰器与yieldgetPage(url)执行上下文切换到Twisted事件循环中一起工作。

事实上,事件循环是隐含的,但在Twisted中,它是由reactor.run()程序底部附近的语句行明确提供的。

"""example_7.py Just a short example demonstrating a simple state machine in PythonThis version is doing actual work, downloading the contents ofURL's it gets from a work_queue. This version uses the Twistedframework to provide the concurrency""" fromtwisted.internetimportdeferfromtwisted.web.clientimportgetPagefromtwisted.internetimportreactor,task importqueuefromlib.elapsed_timeimportET  @defer.inlineCallbacksdefmy_task(name,work_queue):    try:        whilenotwork_queue.empty():            url=work_queue.get()            print(f'Task {name} getting URL: {url}')            et=ET()            yieldgetPage(url)            print(f'Task {name} got URL: {url}')            print(f'Task {name} total elapsed time: {et():.1f}')    exceptExceptionase:        print(str(e))  defmain():    """    This is the main entry point for the program    """    # create the work_queue of 'work'    work_queue=queue.Queue()     # put some 'work' in the work_queue    forurlin[        b"http://google.com",        b"http://yahoo.com",        b"http://linkedin.com",        b"http://shutterfly.com",        b"http://mypublisher.com",        b"http://facebook.com"    ]:        work_queue.put(url)     # run the tasks    et=ET()    defer.DeferredList([        task.deferLater(reactor,0,my_task,'One',work_queue),        task.deferLater(reactor,0,my_task,'Two',work_queue)    ]).addCallback(lambda_:reactor.stop())     # run the event loop    reactor.run()     print()    print(f'Total elapsed time: {et():.1f}')  if __name__ =='__main__':    main()


注意最终结果与gevent版本相同,总的程序运行时间小于要检索的每个URL的累计时间。

 

 

 

示例8:使用Twisted回调的异步(非阻塞)HTTP下载

该版本的程序(example_8.py)也使用Twisted库,但显示了使用Twisted的更传统的方法。

我的意思是,而不是使用@defer.inlineCallbacks/ yield编码风格,这个版本使用明确的回调。“回调”是一个传递给系统的功能,可以稍后调用以响应事件。在下面的例子中,该success_callback()函数被提供给Twisted以在getPage(url)呼叫完成时被调用。

注意在程序中,@defer.inlineCallbacks装饰器不再存在于my_task()函数中。另外,该函数产生一个称为dshort 的变量,称为deferred,它是getPage(url)函数调用返回的内容。

一个延期是处理异步编程扭曲的方式,而这也正是回调连接到。当这个延迟“触发”(当getPage(url)完成时),回调函数将被调用与回调被附加时定义的变量。

"""example_8.py Just a short example demonstrating a simple state machine in PythonThis version is doing actual work, downloading the contents ofURL's it gets from a queue. This version uses the Twistedframework to provide the concurrency""" fromtwisted.internetimportdeferfromtwisted.web.clientimportgetPagefromtwisted.internetimportreactor,task importqueuefromlib.elapsed_timeimportET  defsuccess_callback(results,name,url,et):    print(f'Task {name} got URL: {url}')    print(f'Task {name} total elapsed time: {et():.1f}')  defmy_task(name,queue):    ifnotqueue.empty():        whilenotqueue.empty():            url=queue.get()            print(f'Task {name} getting URL: {url}')            et=ET()            d=getPage(url)            d.addCallback(success_callback,name,url,et)            yieldd  defmain():    """    This is the main entry point for the program    """    # create the queue of 'work'    work_queue=queue.Queue()     # put some 'work' in the queue    forurlin[        b"http://google.com",        b"http://yahoo.com",        b"http://linkedin.com",        b"http://shutterfly.com",        b"http://mypublisher.com",        b"http://facebook.com"    ]:        work_queue.put(url)     # run the tasks    et=ET()     # create cooperator    coop=task.Cooperator()     defer.DeferredList([        coop.coiterate(my_task('One',work_queue)),        coop.coiterate(my_task('Two',work_queue)),    ]).addCallback(lambda_:reactor.stop())     # run the event loop    reactor.run()     print()    print(f'Total elapsed time: {et():.1f}')  if __name__ =='__main__':    main()


运行该程序的最终结果与前两个例子相同,程序的总时间少于获取URL的累计时间。

无论你使用gevent还是Twisted,都是个人喜好和编码风格的问题。两者都是功能强大的库,它们提供了允许程序员创建异步代码的机制。

 

 

 

结论

我希望这可以帮助您了解并理解异步编程在何处以及如何使用。如果你正在编写一个将PI计算到百万分之一的程序,那么异步代码根本就不会起作用。

但是,如果您尝试实施服务器或执行大量IO的程序,则可能会产生巨大的差异。这是一个强大的技术,可以把你的程序到一个新的水平。

 

 

 














原文:https://dbader.org/blog/understanding-asynchronous-programming-in-python#

原创粉丝点击