Python并发之异步编程


1. 可迭代(Iterable)

一个对象只要实现了只要实现了__iter__()方法,那么用isinstance()函数检查就是可迭代(Iterable)对象。

一个可迭代(Iterable)类:

class IterObj:
    def __iter__(self):
        # 这里简单地返回自身
        # 但实际情况可能不会这么写
        # 而是通过内置的可迭代对象来实现
        # 下文的列子中将会展示
        return self 

if __name__ == "__main__":
    it = IterObj()
    print(isinstance(it, Iterable))  # true

    print(isinstance(it, Iterator))  # false
    print(isinstance(it, Generator)) # false

1.1. 常见的可迭代对象

  1. 集合或序列类型(如list、tuple、set、dict、str)
  2. 文件对象
  3. 类中定义了__iter__()方法
print(isinstance([], Iterable))     # true list 是可迭代的
print(isinstance({}, Iterable))     # true 字典是可迭代的
print(isinstance((), Iterable))     # true 元组是可迭代的
print(isinstance(set(), Iterable))  # true set是可迭代的
print(isinstance('', Iterable))     # true 字符串是可迭代的

currPath = os.path.dirname(os.path.abspath(__file__))
with open(currPath+'/model.py') as file:
    print(isinstance(file, Iterable)) # true

2. 迭代器(Iterator)

一个对象实现了__iter__()和__next__()方法,那么它就是一个迭代器(Iterator)对象。

一个迭代器(Iterator)类:

class IterObj:

    def __init__(self):
        self.a = [3, 5, 7, 11, 13, 17, 19]

        self.n = len(self.a)
        self.i = 0

    def __iter__(self):
        return iter(self.a)

    def __next__(self):
        while self.i < self.n:
            v = self.a[self.i]
            self.i += 1
            return v
        else:
            self.i = 0
            raise StopIteration()

if __name__ == "__main__":
    it = IterObj()
    print(isinstance(it, Iterable)) # true
    print(isinstance(it, Iterator)) # true

    print(isinstance(it, Generator)) # false
    print(hasattr(it, "__iter__")) # true
    print(hasattr(it, "__next__")) # true


    print(isinstance([], Iterator)) # false
    print(isinstance({}, Iterator)) # false
    print(isinstance((), Iterator)) # false
    print(isinstance(set(), Iterator)) # false
    print(isinstance('', Iterator)) # false
    
    currPath = os.path.dirname(os.path.abspath(__file__))
    with open(currPath+'/model.py') as file:
        print(isinstance(file, Iterator)) # true

一个迭代器(Iterator)对象不仅可以在for循环中使用,还可以通过内置函数next()函数进行调用。

it = IterObj()
next(it) # 3
next(it) # 5

iter()函数:将可迭代(Iterable)对象转换成迭代器(Iterator)对象

3. 生成器(Generator)

一个生成器(Generator)对象既是可迭代(Iterable)对象也是迭代器(Iterator)对象。

定义生成器有2种方式:

  1. 列表生成器
  2. 使用yield定义生成器函数

第一种方式:

扫描二维码关注公众号,回复: 9580439 查看本文章
g = (x * 2 for x in range(10)) # 0~18的偶数生成器 
print(isinstance(g, Iterable)) # true
print(isinstance(g, Iterator)) # true
print(isinstance(g, Generator)) # true
print(hasattr(g, "__iter__")) # true
print(hasattr(g, "__next__")) # true
print(next(g)) # 0
print(next(g)) # 2

第二种方式:

def gen():
    for i in range(5):
        yield i 

if __name__ == "__main__":
    g1 = gen()
    print('g1: ', next(g1))
    print('g1: ', next(g1))
    print('g1: ', next(g1))

    g2 = gen()
    for i in g2:
        print('g2: ', i)

'''结果显示
g1:  0
g1:  1
g1:  2
g2:  0
g2:  1
g2:  2
g2:  3
g2:  4
'''

这里yield的作用就相当于return,这个函数就是顺序地返回[0, 5)的之间的自然数,可以通过next()或使用for循环来遍历。

3.1. 生产者(producer)-消费者(consumer)模型

基于生成器的协程。

def producer(c):
# 程序运行步骤 -------------------------> 6 
    n = 0
    while n < 5:
        n += 1
        print('producer {}'.format(n))
# 程序运行步骤 -------------------------> 7 第一个while循环,将n传递到 4 的位置, n为1
# 程序运行步骤 -------------------------> 11 从10处返回'ok',赋值给此处的r
        r = c.send(n)
        print('consumer return {}'.format(r))
# 程序运行步骤 -------------------------> 12 第一个while循环走完,打印
# 程序运行步骤 -------------------------> 13 开始第二个while循环

def consumer():
# 程序运行步骤 -------------------------> 3 
    r = ''
    while True:
# 程序运行步骤 -------------------------> 4 第一个while循环,运行到yield r,程序返回r,值为''
# 程序运行步骤 -------------------------> 8 在 7 处使用send(n)传递producer中的n,赋值给此处的n
# 程序运行步骤 -------------------------> 10 第二个while循环,运行到yield r,程序返回r,值为'ok'
        n = yield r
        if not n:
            return
        print('consumer {} '.format(n))
        r = 'ok'
# 程序运行步骤 -------------------------> 9 第一个while循环走完,r被赋值为'ok'

if __name__ == '__main__':
# 程序运行步骤 -------------------------> 0 程序开始,返回类型为生成器(generator)的c对象
    c = consumer()
# 程序运行步骤 -------------------------> 1 第一次调用consumer()
    # next(c)       # 启动consumer
    c.send(None)    # 启动consumer
# 程序运行步骤 -------------------------> 5 调用producer()
    producer(c)

'''结果显示
producer 1
consumer 1
consumer return ok
producer 2
consumer 2
consumer return ok
producer 3
consumer 3
consumer return ok
producer 4
consumer 4
consumer return ok
producer 5
consumer 5
consumer return ok
'''

协程实现了CPU在两个函数之间进行切换从而实现并发的效果。

4. 何为协程(Coroutine)

协程(Coroutine)是在线程中执行的,可理解为微线程,它比线程更加轻量些。
一个协程可以随时中断自己让另一个协程开始执行,也可以从中断处恢复并继续执行,它们之间的调度是由程序员来控制的(可以看本文开篇处生产者-消费者的代码)。

4.1. 定义一个协程

在函数定义时用async声明就定义了一个协程。

import asyncio

# 定义了一个简单的协程
async def simple_async():
    print('hello')
    await asyncio.sleep(1) # 休眠1秒
    print('python')
    
# 使用asynio中run方法运行一个协程
asyncio.run(simple_async())

# 执行结果为
# hello
# python

在协程中如果要调用另一个协程就使用await。

注意:await关键字要在async定义的函数中使用,而反过来async函数可以不出现await。

asyncio.run()将运行传入的协程,负责管理asyncio事件循环。
除了run()方法可直接执行协程外,还可以使用事件循环loop

import asyncio
async def do_something(index):
    print(f'start {time.strftime("%X")}', index)
    await asyncio.sleep(1)
    print(f'finished at {time.strftime("%X")}', index)

def test_do_something():
    # 生成器产生多个协程对象
    task = [ do_something(i) for i in range(5) ]

    # 获取一个事件循环对象
    loop = asyncio.get_event_loop()
    # 在事件循环中执行task列表
    loop.run_until_complete(asyncio.wait(task))
    loop.close()

test_do_something()

'''结果显示
start 00:04:03 3
start 00:04:03 4
start 00:04:03 1
start 00:04:03 2
start 00:04:03 0
finished at 00:04:04 3
finished at 00:04:04 4
finished at 00:04:04 1
finished at 00:04:04 2
finished at 00:04:04 0
'''

5. 可等待(Awaitable)对象

可等待对象有:

  1. 协程(corountine)
  2. 任务(Task)
  3. Future

这些对象可以使用await关键字进行调用。await awaitable_object

5.1. 协程(corountine)

协程由async def声明定义,一个协程可由另一个协程使用await进行调用。

import asyncio
async def nested():
    print('in nested func')
    return 13

async def outer():
    # 要使用await 关键字 才会执行一个协程函数返回的协程对象
    print(await nested())

asyncio.run(outer())

'''结果显示
in nested func
13
'''

如果在outer()方法中直接调用nested()而不使用await,将抛出一个RuntimeWarning

import asyncio
async def nested():
    print('in nested func')
    return 13

async def outer():
    # 直接调用协程函数不会发生执行,只是返回一个协程(corountine)对象
    nested()
    
asyncio.run(outer())

'''结果显示
RuntimeWarning: coroutine 'nested' was never awaited
  nested()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
'''

5.2. 任务(Task)

用来并发地执行协程。
可以使用asyncio.create_task()将一个协程对象封装成任务,该任务将很快被排入调度队列并执行。

import asyncio
async def nested():
    print('in nested func')
    return 13

async def create_task():
    # create_task 将一个协程对象打包成一个任务时,该协程就会被自动调度运行
    task = asyncio.create_task(nested())
    # 如果要看到task的执行结果
    # 可以使用await等待协程执行完成,并返回结果
    ret = await task
    print(f'nested return {ret}')

asyncio.run(create_task())

'''结果显示
in nested func
nested return 13
'''

5.3. Future

一种特殊的低层级(low-level)对象,它是异步操作的最终结果(eventual result)。
当一个 Future 对象被等待,这意味着协程将保持等待直到该 Future 对象在其他地方操作完毕。

通常在应用层代码不会直接创建Future对象。
在某些库和asyncio模块中的会使用到该对象。

6. 并发

6.1. Task

Task可以并发地执行。
asyncio.create_task()就是一个把协程封装成Task的方法。

import asyncio
import time

async def do_after(what, delay):
    await asyncio.sleep(delay)
    print(what)

# 利用asyncio.create_task创建并行任务
async def corun():
    task_hello = asyncio.create_task(do_after('hello', 1)) # 模拟执行1秒的任务
    task_python = asyncio.create_task(do_after('python', 2)) # 模拟执行2秒的任务

    print(f'started at {time.strftime("%X")}')
    # 等待两个任务都完成,两个任务是并行的,所以总时间两个任务中最大的执行时间
    await task_python
    await task_hello
    print(f'finished at {time.strftime("%X")}')

asyncio.run(corun())

'''结果显示
started at 20:53:45
hello
python
finished at 20:53:47
'''

task_hello是一个执行1秒的任务,task_python是一个执行2秒的任务,两个任务并发的执行,总共消耗2秒。

6.2. gather

gather也可以并发的执行。
asyncio.gather()接收协程参数列表。

import asyncio
import time

async def do_after(what, delay):
    await asyncio.sleep(delay)
    print(what)
    
async def gather():
    print(f'started at {time.strftime("%X")}')
    # 使用gather可将多个协程传入
    await asyncio.gather(
        do_after('hello', 1),
        do_after('python', 2),
    )
    print(f'finished at {time.strftime("%X")}')

asyncio.run(gather())

'''结果显示
started at 23:47:50
hello
python
finished at 23:47:52
'''

7. 事件循环(Event Loop)

asyncio模块中的核心就是事件循环(Event Loop)。
asncio模块的很多高级接口是通过封装Event Loop对象来实现的。

asyncio模块可用于:

  1. 执行异步任务
  2. 事件回调
  3. 执行网络IO操作
  4. 运行子进程

注意:尽量使用asyncio包提供的高级的API,避免直接使用Event Loop对象中的方法。系统提供的底层能力的功能模块例如网络连接、文件IO等都会使用到loop。

7.1. 获取Event Loop对象

  • asyncio.get_running_loop()
    获取当前系统线程正在使用的loop对象。

  • asyncio.get_event_loop()
    获取当前正在使用的loop对象
    如果当前系统线程还没有loop对象,那么就会创建一个新的loop对象,并使用asyncio.set_event_loop(loop)方法设置到当前系统线程中

  • asyncio.new_event_loop()
    创建一个新的loop对象

  • asyncio.set_event_loop(loop)
    将loop设置成系统线程使用的对象

7.2. Event Loop对象的常用方法

7.2.1. 启动和停止

  • loop.run_until_complete(future)
    future对象执行完成才返回

  • loop.run_forever()
    一直运行,直到调用了loop.stop()方法

  • loop.stop()
    停止loop对象

  • loop.is_running()
    判断loop是否正在运行

  • loop.is_closed()
    判断loop是否关闭

  • loop.close()
    关闭loop对象

  • coroutine loop.shutdown_asyncgens()

try:
    loop.run_forever()
finally:
    loop.run_until_complete(loop.shutdown_asyncgens())
    loop.close()

7.2.2. 回调方法

  • loop.call_soon(callback, *args, context=None)
    在事件循环的下一次迭代中执行callback方法,args是方法中的参数

  • loop.call_soon_threadsafe(callback, *args, context=None)
    线程安全的call_soon()

import asyncio
import time

def hello_world(loop):
    print('Hello World')
    time.sleep(3)  # 模拟长时间操作
    loop.stop()

loop = asyncio.get_event_loop()

# 使用loop执行 hello_world()
loop.call_soon(hello_world, loop)

# 会一直阻塞,直到调用了stop方法
try:
    loop.run_forever()
finally:
    loop.close()

7.2.3. 可延迟的回调方法

可设置延迟执行的方法:

  • loop.call_later(delay, callback, *args, context=None)
    延迟delay秒后执行

  • loop.call_at(when, callback, *args, context=None)
    在指定时间点执行

  • loop.time()
    返回当前时间

import asyncio
import datetime

def display_date(end_time, loop):
    print(datetime.datetime.now())
    if (loop.time() + 1.0) < end_time:
        # 1秒后执行
        loop.call_later(1, display_date, end_time, loop)
    else:
        loop.stop()

loop = asyncio.get_event_loop()

# 执行5秒
end_time = loop.time() + 5.0
loop.call_soon(display_date, end_time, loop)

# 一直运行,等待stop的调用
try:
    loop.run_forever()
finally:
    loop.close()

7.2.4. 创建Future和Tasks

  • loop.create_future()
    创建一个绑定事件循环的future对象

  • loop.create_task(coro)
    将coro放入调度,并返回task对象

  • loop.set_task_factory(factory)
    设置任务工厂

  • loop.get_task_factory()
    返回任务工厂,有可能返回None

7.2.5. 创建网络连

  • coroutine loop.create_datagram_endpoint(protocol_factory, local_addr=None, remote_addr=None, *, family=0, proto=0, flags=0, reuse_address=None, reuse_port=None, allow_broadcast=None, sock=None)
    创建UDP连接

  • coroutine loop.create_unix_connection(protocol_factory, path=None, *, ssl=None, sock=None, server_hostname=None, ssl_handshake_timeout=None)
    创建Unix连接

  • coroutine loop.create_server(protocol_factory, host=None, port=None, *, family=socket.AF_UNSPEC, flags=socket.AI_PASSIVE, sock=None, backlog=100, ssl=None, reuse_address=None, reuse_port=None, ssl_handshake_timeout=None, start_serving=True)
    创建TCP服务

  • coroutine loop.create_unix_server(protocol_factory, path=None, *, sock=None, backlog=100, ssl=None, ssl_handshake_timeout=None, start_serving=True)
    创建Unix服务

  • coroutine loop.connect_accepted_socket(protocol_factory, sock, *, ssl=None, ssl_handshake_timeout=None)
    封装已建立的连接,返回元组(transport, protocol)

7.2.6. Event Loop 的实现

asyncio的事件循环有两种不同的实现:

  • SelectorEventLoop
  • ProactorEventLoop

它们的父类是AbstractEventLoop。

7.2.6.1. SelectorEventLoop

这个是asyncio默认使用的Event Loop实现,支持unix和windows平台

import asyncio
import selectors

selector = selectors.SelectSelector()
loop = asyncio.SelectorEventLoop(selector)
asyncio.set_event_loop(loop)

7.2.6.2. ProactorEventLoop

这个是Windows平台专有的实现

import asyncio
import sys

if sys.platform == 'win32':
    loop = asyncio.ProactorEventLoop()
    asyncio.set_event_loop(loop)

8. 其他例子

import time
import random
import asyncio

async def waiter(name):
    for _ in range(4):
        time_to_sleep = random.randint(1, 3) / 4
        # 阻塞程序,但释放控制权
        await asyncio.sleep(time_to_sleep)
        print("{} waited {} seconds".format(name, time_to_sleep))

async def main():
    await asyncio.wait([waiter("foo"), waiter("bar")])

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
    loop.close()

'''结果显示
bar waited 0.25 seconds
foo waited 0.5 seconds
bar waited 0.25 seconds
bar waited 0.25 seconds
foo waited 0.5 seconds
bar waited 0.25 seconds
foo waited 0.5 seconds
foo waited 0.5 seconds
'''
import asyncio
async def print_number(number):
    print(number)

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(
        asyncio.wait(
            # 列表推导式
            [ print_number(number) for number in range(10) ] 
        )
    )
    loop.close()

'''结果显示
3
7
6
9
4
8
0
2
5
1
'''

9. 其他参考

https://python-parallel-programmning-cookbook.readthedocs.io/zh_CN/latest/index.html

https://juejin.im/post/5ccafbf5e51d453a3a0acb42

https://juejin.im/post/5ccf0d18e51d453b557dc340

发布了31 篇原创文章 · 获赞 11 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/MrRight17/article/details/104543979