python 基于异步的编程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yangxiaodong88/article/details/80942790

背景

除了顺序执行和并行执行的模型之外,还有第三种模型,叫做异步模型,这是事件驱动模型的基础。异步活动的执行模型可以只有一个单一的主控制流,能在单核心系统和多核心系统中运行。

在并发执行的异步模型中,许多任务被穿插在同一时间线上,所有的任务都由一个控制流执行(单一线程)。任务的执行可能被暂停或恢复,中间的这段时间线程将会去执行其他任务。下面的这幅图可以清楚地表达这个概念。
这里写图片描述

如上图所示, 任务(不同颜色代替不同的任务) 可能被其他的任务插入, 但是都处于同一线程,下。 这表明, 当某一个任务执行时候, 其他任务都暂停啦。 与多线程编程模型不同的是, 多线程由操作系统决定在时间线上面什么时候挂起某个活动或者恢复某个活动, 而在异步编程模型中, 程序员必须假设线程可能在任何时间被挂起和替换。

程序员可以将任务写成许多可以间隔执行的小步骤,这样的化如果一个任务需要另外一个任务的输出, 那么被依赖的任务必须接受他的输入

使用python 中的concurrent.futures 模块

python3.2 带来了concurrent.future 模块, 这个模块具有线程池和进程池, 管理并行编程任务, 处理非确定性的执行流程, 进程/线程同步等功能
此模块由以下部分组成:

  • concurrent.futures.Executor: 这是一个虚拟基类,提供了异步执行的方法。
  • submit(function, argument): 调度函数(可调用的对象)的执行,将 argument 作为参数传入。
  • map(function, argument): 将 argument 作为参数执行函数,以 异步 的方式。
  • shutdown(Wait=True): 发出让执行者释放所有资源的信号。
  • concurrent.futures.Future: 其中包括函数的异步执行。Future对象是submit任务(即带有参数的functions)到executor的实例。

使用线程池和进程池

线程池或者进程池是用于在程序中优化和简化进程线程的使用。 通过池, 你可以提交任务给executor。 池由两部分组成, 一部分是内部的队列, 存放着待执行的任务;另一部分是一系列的进程和线程, 用于执行这些任务。池的概念主要目的是为了重用: 让线程或者进程在生命周期内可以多次使用。 它减少了创建线程和进程的开销, 提高了程序的性能。 重用不是必须的规则, 但是它是程序员在应用中使用池的主要原因。
这里写图片描述

准备工作

current.Futures 模块提供了两种Executor 的子类,各自独立操作一个线程池和进程池

concurrent.futures.ThreadPoolExecutor(max_works)
concurrent.futures.ProcessPoolExecutor(max_works)

下面展示 线程池进程池的功能

给一个list number_list ,包含1到10。对list中的每一个数字,乘以1+2+3…+10000000的和(这个任务只是为了消耗时间)
分别测试

  • 顺序执行
  • 通过有5个worker的线程池执行
  • 通过有5个worker的进程池执行
import concurrent.futures
import time

# number_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
number_list = [1, 2, 3, 4, 5]


def evaluate_item(x):
    # 计算总和,这里只是为了消耗时间
    result_item = count(x)
    # 打印输入和输出结果
    return result_item


def count(number):
    for i in range(0, 10000000):
        i = i + 1
    return i * number


if __name__ == "__main__":
    # 顺序执行
    start_time = time.time()
    for item in number_list:
        print(evaluate_item(item))
    print("Sequential execution in " + str(time.time() - start_time), "seconds")
    # 线程池执行
    start_time_1 = time.time()
    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
        futures = [executor.submit(evaluate_item, item) for item in number_list]
        for future in concurrent.futures.as_completed(futures):
            print(future.result())
    print("Thread pool execution in " + str(time.time() - start_time_1), "seconds")
    # 进程池
    start_time_2 = time.time()
    with concurrent.futures.ProcessPoolExecutor(max_workers=5) as executor:
        futures = [executor.submit(evaluate_item, item) for item in number_list]
        for future in concurrent.futures.as_completed(futures):
            print(future.result())
    print("Process pool execution in " + str(time.time() - start_time_2), "seconds")

运行结果

10000000
20000000
30000000
40000000
50000000
Sequential execution in 2.020594358444214 seconds
10000000
50000000
20000000
30000000
40000000
Thread pool execution in 2.110356330871582 seconds
10000000
30000000
40000000
20000000
50000000
Process pool execution in 0.7988626956939697 seconds

可以看到线程池和普通的计算方法执行的时间没有什么差别, 但是和进程池 差了很多。

ThreadPoolExecutor 和ProcessPoolExecutor 不同的是。 后者是多核处理的模块。 让我们可以不受GIL的限制, 大大缩短了执行流程。

注意
几乎所有需要处理多个客户端请求的服务应用都会使用到池。 然而有一些应用要求任务需要立即执行, 或者要求对任务的线程有更多的控制权。这种情况下池不是一个最佳的选择

使用Asyncion 管理时间循环

Python的 Asyncion 模块提供啦管理事件, 协程, 任务和线程的方法, 以及编写并发代码原语。 此模块的组件和概念包括:

  • 事件循环: 在Asyncio模块中, 每一个进程都是一个事件循环。
  • 协程 :这是子程序的泛化概念。 协程可以在执行期间暂停。 这样就可以等待外部的处理 例如(IO)完成之后, 从之前的停止位置恢复执行。
  • Futures : 定义了Future对象, 和concurrent.futures模块一样, 表示尚未完成的计算。
  • Tasks:这是Asyncio 的子类, 用于封装和管理并行模式下的协程。

异步编程的上下文中, 事件是无比的重要, 因为事件的本质就是异步。

什么是事件循环

在计算机系统中, 可以产生事件实体的叫做事件源, 能处理事件的实体叫做事件的处理者。 此外还有第三方实体叫做 事件循环 。 它的作用是管理所有的事件,在整个程序运行的过程中不断循环执行, 追踪事件发生的顺序, 把他们放在队列中, 当主线程空闲的时候, 调用相应的事件处理者处理事件。 最后通过伪代码来理解一下:

while (1) {
events = getEvents();
for (e in events)
    processEvent(e);
}

所有的事件都在while循环中捕捉, 然后经过事件处理者处理,事件处理的部分是系统唯一活跃的部分, 当一个事件处理完成, 流程继续处理下一个事件。

准备

  • loop = get_event_loop(): 得到当前上下文的事件循环。
  • loop.call_later(time_delay, callback, argument): 延后time_delay秒载执行callback 方法
  • loop.call_soon(callback, argument): 尽可能快的调用callback, callsooon() 函数结束, 主线程回到事件循环之后就会马上调用callback().
  • loop.time() 以float类型返回当前事件循环的内部时间。
  • asyncio.set_event_loop() 为当前上下文设置事件循环。
  • asyncio.new_event_loop: 根据此策略创建一个新的时间循环并返回
  • loop.run_forever(): 在调用stop() 之前将一直运行
# Author : July  Yang 

import asyncio
import datetime
import time

def function_1(end_time, loop):
    print ("function_1 called")
    if (loop.time() + 1.0) < end_time:
        loop.call_later(1, function_2, end_time, loop)
    else:
        loop.stop()

def function_2(end_time, loop):
    print ("function_2 called ")
    if (loop.time() + 1.0) < end_time:
        loop.call_later(1, function_3, end_time, loop)
    else:
        loop.stop()

def function_3(end_time, loop):
    print ("function_3 called")
    if (loop.time() + 1.0) < end_time:
        loop.call_later(1, function_1, end_time, loop)
    else:
        loop.stop()

def function_4(end_time, loop):
    print ("function_5 called")
    if (loop.time() + 1.0) < end_time:
        loop.call_later(1, function_4, end_time, loop)
    else:
        loop.stop()

loop = asyncio.get_event_loop()

end_loop = loop.time() + 9.0
loop.call_soon(function_1, end_loop, loop)
# loop.call_soon(function_4, end_loop, loop)
loop.run_forever()
loop.close()

运行程序

function_1 called
function_2 called 
function_3 called
function_1 called
function_2 called 
function_3 called
function_1 called
function_2 called 
function_3 called

使用asyncio 管理协程

当一个程序变得很大的时候而且复杂时候, 将其划分为子程序, 每一部分实现特定的任务是个不错的方案。 子程序不能单独执行, 只能在主程序的请求下执行,主程序负责协调各个主程序。 协程就是子程序的泛化。 和子程序一样的事, 协程只负责计算任务的一步, 和子程序不一样的是, 协程没有主程序来调度, 。 这是因为协程是通过管道连接在一起的, 没有监视函数负责调度他们。 在协程中执行点可以被挂起, 可以从之前被挂起的点恢复执行。通过协程池可以插入到计算中: 运行第一个任务, 直到它返回 yield 执行权, 然后运行下一个, 这样顺着执行下去。

这种插入的控制组件就是事件循环。 它持续追踪所有协程并执行他们。

协程的其他重要属性
1 协程可以有多个入口, 并可以yield多次
2 协程可以将执行权交给其他的协程

yield 表示协程暂停, 并且将执行权交给其他的协程, 因为协程可以将值与控制权一起传递给另一个协程, 所以yield 一个值 就表示将值传递给下一个执行的协程。

使用这个比较简单

# Author : July  Yang 
import asyncio

@asyncio.coroutine
def coroution_function(function_argument):
    pass

在这个例子中,我们将看到如何使用Asyncio的协程来模拟有限状态机。有限状态机(finite state machine or automaton, FSA)是一个数学模型,不仅在工程领域应用广泛,在科学领域也很著名,例如数学和计算机科学等。我们要模拟的状态机如下图所示:
这里写图片描述

在上图中,可以看到我们的系统有 S1, S2, S3, S4 四个状态, 0 和 1 是状态机可以从一个状态到另一个状态的值(这个过程叫做转换)。例如在本实验中,只有当只为1的时候, S0 可以转换到 S1 ,当只为0的时候, S0 可以转换到 S2 .Python代码如下,状态模拟从 S0 开始,叫做 初始状态 ,最后到 S4 ,叫做 结束状态 。

import asyncio
import time
from random import randint


@asyncio.coroutine
def StartState():
    print("Start State called \n")
    input_value = randint(0, 1)
    time.sleep(1)
    if (input_value == 0):
        result = yield from State2(input_value)
    else:
        result = yield from State1(input_value)
    print("Resume of the Transition : \nStart State calling " + result)

@asyncio.coroutine
def State1(transition_value):
    outputValue =  str("State 1 with transition value = %s \n" % transition_value)
    input_value = randint(0, 1)
    time.sleep(1)
    print("...Evaluating...")
    if input_value == 0:
        result = yield from State3(input_value)
    else :
        result = yield from State2(input_value)
    result = "State 1 calling " + result
    return outputValue + str(result)

@asyncio.coroutine
def State2(transition_value):
    outputValue =  str("State 2 with transition value = %s \n" % transition_value)
    input_value = randint(0, 1)
    time.sleep(1)
    print("...Evaluating...")
    if (input_value == 0):
        result = yield from State1(input_value)
    else :
        result = yield from State3(input_value)
    result = "State 2 calling " + result
    return outputValue + str(result)

@asyncio.coroutine
def State3(transition_value):
    outputValue = str("State 3 with transition value = %s \n" % transition_value)
    input_value = randint(0, 1)
    time.sleep(1)
    print("...Evaluating...")
    if (input_value == 0):
        result = yield from State1(input_value)
    else :
        result = yield from EndState(input_value)
    result = "State 3 calling " + result
    return outputValue + str(result)

@asyncio.coroutine
def EndState(transition_value):
    outputValue = str("End State with transition value = %s \n" % transition_value)
    print("...Stop Computation...")
    return outputValue

if __name__ == "__main__":
    print("Finite State Machine simulation with Asyncio Coroutine")
    loop = asyncio.get_event_loop()
    loop.run_until_complete(StartState())

注意:
被@asyncio.coroutine 修饰的函数不能直接 被调用 要使用yield from

使用Asyncio 控制任务

Asyncio 是用来处理事件循环中的异步进程和并发任务的。它还提供asyncio.Task() 类, 可以在任务中使用协程。 他的作用是, 在同一事件循环中, 运行某一个任务的同时可以并发的运行多个任务。 当协程被包含在任务中, 它会自动的将任务和事件循环连接起来, 当事件循环启动的时候, 任务自动运行。 这样就提供了一个可以自动驱动协程的机制。

准备工作

Adyncio 模块为我们准备了asyncio.Task(coroutine) 方法来处理计算任务, 它可以调度协程的执行。 任务对协程对象在事件循环的执行负责。 如果被包裹的协程要从future yield 那么会被挂起, 等待future的处理结果。

当future 计算完成, 被包裹的协程将会拿到future返回的结果或者异常 继续执行。事件循环一次只能运行一个任务, 除非还有其他的事件循环再不同的线程并行运行,此任务才能和其他任务并行。 当一个任务在等待future 执行的期间, 事件会循环运行一个新的任务。

"""
Asyncio using Asyncio.Task to execute three math function in parallel
"""
import asyncio
@asyncio.coroutine
def factorial(number):
    f = 1
    for i in range(2, number + 1):
        print("Asyncio.Task: Compute factorial(%s)" % (i))
        yield from asyncio.sleep(1)
        f *= i
    print("Asyncio.Task - factorial(%s) = %s" % (number, f))

@asyncio.coroutine
def fibonacci(number):
    a, b = 0, 1
    for i in range(number):
        print("Asyncio.Task: Compute fibonacci (%s)" % (i))
        yield from asyncio.sleep(1)
        a, b = b, a + b
    print("Asyncio.Task - fibonacci(%s) = %s" % (number, a))

@asyncio.coroutine
def binomialCoeff(n, k):
    result = 1
    for i in range(1, k+1):
        result = result * (n-i+1) / i
        print("Asyncio.Task: Compute binomialCoeff (%s)" % (i))
        yield from asyncio.sleep(1)
    print("Asyncio.Task - binomialCoeff(%s , %s) = %s" % (n, k, result))

if __name__ == "__main__":
    tasks = [asyncio.Task(factorial(10)),
             asyncio.Task(fibonacci(10)),
             asyncio.Task(binomialCoeff(20, 10))]
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()

asyncio.wait(tasks) 表示运行直到所有给定的协程都完成

猜你喜欢

转载自blog.csdn.net/yangxiaodong88/article/details/80942790