python、协程、理解 async、await、具体应用例子代码理解

import random
import time

random.seed(1234)

import logging
from pprint import pprint
from sys import stdout as STDOUT

# Write all output to a temporary directory
import atexit
import gc
import io
import os
import tempfile

TEST_DIR = tempfile.TemporaryDirectory()
atexit.register(TEST_DIR.cleanup)

# Make sure Windows processes exit cleanly
OLD_CWD = os.getcwd()
atexit.register(lambda: os.chdir(OLD_CWD))
os.chdir(TEST_DIR.name)


def close_open_files():
    everything = gc.get_objects()
    for obj in everything:
        if isinstance(obj, io.IOBase):
            obj.close()


atexit.register(close_open_files)

ALIVE = '*'
EMPTY = '-'


class Grid:
    def __init__(self, height, width):
        self.height = height
        self.width = width
        self.rows = []
        for _ in range(self.height):
            self.rows.append([EMPTY] * self.width)

    def get(self, y, x):
        return self.rows[y % self.height][x % self.width]

    def set(self, y, x, state):
        self.rows[y % self.height][x % self.width] = state

    def __str__(self):
        output = ''
        for row in self.rows:
            for cell in row:
                output += cell
            output += '\n'
        return output




import asyncio


async def simulate(grid):
    next_grid = Grid(grid.height, grid.width)

    tasks = []
    for y in range(grid.height):
        for x in range(grid.width):
            task = step_cell(
                y, x, grid.get, next_grid.set)  # Fan out
            tasks.append(task)
    # await asyncio.sleep(1)
    print("simulate方法里")
    await asyncio.gather(*tasks)  # Fan in
    print("gather方法下一行")
    return next_grid


class ColumnPrinter:
    def __init__(self):
        self.columns = []

    def append(self, data):
        self.columns.append(data)

    def __str__(self):
        row_count = 1
        for data in self.columns:
            row_count = max(
                row_count, len(data.splitlines()) + 1)

        rows = [''] * row_count
        for j in range(row_count):
            for i, data in enumerate(self.columns):
                line = data.splitlines()[max(0, j - 1)]
                if j == 0:
                    padding = ' ' * (len(line) // 2)
                    rows[j] += padding + str(i) + padding
                else:
                    rows[j] += line

                if (i + 1) < len(self.columns):
                    rows[j] += ' | '

        return '\n'.join(rows)

async def count_neighbors(y, x, get):
    # 原来是普通方法,自己修改成协程方法
    print("【进入count_neighbors方法】", time.time())
    await asyncio.sleep(1)
    n_ = get(y - 1, x + 0)  # North
    ne = get(y - 1, x + 1)  # Northeast
    e_ = get(y + 0, x + 1)  # East
    se = get(y + 1, x + 1)  # Southeast
    s_ = get(y + 1, x + 0)  # South
    sw = get(y + 1, x - 1)  # Southwest
    w_ = get(y + 0, x - 1)  # West
    nw = get(y - 1, x - 1)  # Northwest
    neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw]
    count = 0
    for state in neighbor_states:
        if state == ALIVE:
            count += 1
    print("【count_neighbors方法结束】", time.time(), f"返回结果count:【{count}】")
    return count


async def step_cell(y, x, get, set):
    state = get(y, x)
    # neighbors = await asyncio.create_task(count_neighbors(y, x, get))
    neighbors = await count_neighbors(y, x, get)
    # await asyncio.sleep(1)
    next_state = await game_logic(state, neighbors)
    set(y, x, next_state)


async def game_logic(state, neighbors):
    print("【进入game_logic方法】", time.time(), f"传入的neighbors:【{neighbors}】")

    last_num = int(str(time.time()).split(".")[0][-1])
    sleep_time = random.randint(1, 5)  # 观察得出:每个协程game_logic暂停的时间不一样,最后结束的时刻也是不同
    # sleep_time = 1 if last_num % 2 == 1 else 5
    print("game_logic中的sleep_time", sleep_time)
    await asyncio.sleep(sleep_time)
    print("【game_logic方法结束】", time.time())
    if state == ALIVE:
        if neighbors < 2:
            return EMPTY  # Die: Too few
        elif neighbors > 3:
            return EMPTY  # Die: Too many
    else:
        if neighbors == 3:
            return ALIVE  # Regenerate

    return state


logging.getLogger().setLevel(logging.ERROR)

grid = Grid(5, 9)
grid.set(0, 3, ALIVE)
grid.set(1, 4, ALIVE)
grid.set(2, 2, ALIVE)
grid.set(2, 3, ALIVE)
grid.set(2, 4, ALIVE)

columns = ColumnPrinter()
for i in range(5):
    columns.append(str(grid))
    print("进入for循环")
    grid = asyncio.run(simulate(grid))
print(columns)

logging.getLogger().setLevel(logging.DEBUG)

 

python、协程、理解 async、await、具体应用例子代码理解 

注:代码引用自书籍【Effective Python】,略做少许修改,同时加了些打印。

========================================  【协程】:开始 =============================================

【知识点】
5.
【基本原理与生成器(generator)类似】
【Effective Python】
5.1
像这种在并发方面要求比较高的I/O需求,可以用Python的协程(coroutine)来解决。协程能够制造出一种效果,让我们觉得Python程序好像真的可以同时执行大量任务。这种效果需要使用async与await关键字来实现,它的基本原理与生成器(generator)类似,也就是不立刻给出所有的结果,而是等需要用到的时候再一项一项地获取(参见第30条、第34条与第35条)。
5.2
协程激活之后,【只占用不到1KB内存】,所以只要内存足够,协程稍微多一些也没关系。

4.
【流畅的python】
没有API能从外部终止线程(Thread),因为线程随时可能被中断,导致系统处于无效状态。
如果想终止任务(Task),可以使用Task.cancel()实例方法,在协程内部抛出CancelledError异常。协程可以在暂停的yield处捕获这个异常,处理终止请求。

3.
【**】【await】
3.1
【await的运行过程】
按照目前观察:await 后面跟着的方法运行结束了、就继续往await下一行运行,await可能会处在while/for循环里循环往复;如果await后面的方法没有结束,就停在await那里继续等待。
按照目前观察:只要有await的地方(或者yield from)、就会等待await后面的awaitable对象执行结束后、再继续await后面的代码
如:
await asyncio.sleep(3)
print("a")
该协程中,await告诉时间循环:我要等待3秒了,事件循环就调度其他的协程;await后面的协程运行结束了(即sleep)、才会接着往下运行下一行代码print
3.2
【代码例子】(通过代码理解协程时、最好多加打印以区分时间运行的先后、多修改代码)
【流畅的python】第18章的例子(await版本、yield from版本都一样)、比python文档中say_after的例子要好理解:await(即yield from)在循环里:for char in itertools.cycle('|/-\\'),
【Effective Python】【例子最好】【for循环每次有45个task、for循环5次】第60节的生命游戏的代码,比【流畅的python】、python文档的例子都更好,可以运行成功、是实际应用的例子
3.2.1
【书籍】
【流畅的python】书中没有讲async def,只有yield from基于生成器的协程;不过18章的代码里倒是有改写并加入async def的例子
【Effective Python】书中有async def,代码也是async def + await的
出版时间从早到晚:【Python Cookbook】->【流畅的python】->【Effective Python】

3.3
【await与控制权的关系】
【疑问】event loop事件循环是怎么调度跟分配task的执行的?
    ...未知【留坑】
【疑问】什么时候获取控制权、什么时候释放控制权:
    【猜测】(同【流畅的python】第18章中的代码解释):
        【释放控制权】yield from执行后释放控制权(交还给事件循环event loop)、
        【获取控制权】yield from 后面跟着的方法(如sleep)结束后,恢复协程、 重新获取控制权
    【A】
    【释放控制权】:
        想交出控制权时,可以使用yield或yield from把控制权交还调度程序。【流畅的python】
    【获取控制权】:
        ...未知【留坑】

2.
【基于生成器的协程】:
@asyncio.coroutine + yield from
如【流畅的python】第18章的例子:
@asyncio.coroutine
def supervisor():  # <7>
    spinner = asyncio.async(spin('thinking!'))  # <8>
    print('spinner object:', spinner)  # <9>
    result = yield from slow_function()  # <10>
    spinner.cancel()  # <11>
    return result
    
    
    
1.
线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员。
【协程存在的意义】:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:
协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
【协程的适用场景】:当程序中存在大量不需要CPU的操作时(IO),适用于协程。

【协程的好处】:
无需线程上下文切换的开销
无需原子操作锁定及同步的开销
方便切换控制流,简化编程模型
高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

【缺点】:
无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
【源自】https://blog.csdn.net/weixin_34194317/article/details/92635546?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-1-92635546-blog-110071488.pc_relevant_3mothn_strategy_recovery&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-1-92635546-blog-110071488.pc_relevant_3mothn_strategy_recovery&utm_relevant_index=2

【协程的使用场景】
1.
协程基于generator,Python3 中内置了异步IO。遇到IO密集型的业务时,总是很费时间啦,多线程加上协程, 在磁盘在读写的同时,还能去做其他的事情,在WEB应用中效果尤为明显。
https://blog.csdn.net/weixin_39589394/article/details/110071488?ops_request_misc=&request_id=&biz_id=102&utm_term=%E5%8D%8F%E7%A8%8B%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-3-110071488.nonecase&spm=1018.2226.3001.4187
2.
协程的适用场景:当程序中存在大量不需要CPU的操作时(IO),适用于协程。
https://blog.csdn.net/weixin_34194317/article/details/92635546?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-1-92635546-blog-110071488.pc_relevant_3mothn_strategy_recovery&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-1-92635546-blog-110071488.pc_relevant_3mothn_strategy_recovery&utm_relevant_index=2

【疑问】
1.
【未解决】
【背景】
协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程https://blog.csdn.net/weixin_39589394/article/details/110071488?ops_request_misc=&request_id=&biz_id=102&utm_term=%E5%8D%8F%E7%A8%8B%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-3-110071488.nonecase&spm=1018.2226.3001.4187
【Q】
协程里的sleep(asyncio.sleep() ),不会阻塞当前所在的线程吗?怎么做到不阻塞的?
==》
【A】
假设不是sleep而是IO等待其他网站返回回应,先执行其他协程、这边等待了3秒再回到该协程继续运行,那么就不存在阻塞的问题。asyncio.sleep()应该是类似网站等待的场景,区别于time.sleep()
2.
asyncio.sleep()是一个coroutine function
"""Coroutine that completes after a given time (in seconds)."""


2.
【未解决】
create_task,直接await、会变成task吗?
await视频里纠正过说不会把coroutine变成task,那没有事先create_task变成task的话、await后面那个协程是怎么运行的?
3.
【未解决】
await的作用有几个?分别是什么?
4.
【未解决】
视频里说:
task对控制权的交还是显性的:1.await方法交换;2.协程函数运行结束后交换。
【Q】
那么task对控制权的获取是什么时候?

========================================  【协程】:结束 =============================================

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值