Trio翻译过来是三重奏的意思,它提供了更方便异步编程,是asyncio的更高级的封装。

它试图简化复杂的asyncio模块。使用起来比asyncio和Twisted要简单的同时,拥有其同样强大功能。这个项目还很年轻,还处于试验阶段但是整体设计是可靠的。作者鼓励大家去尝试使用,如果遇到问题可以在git上对他提issue。同时作者还提供了一个在线聊天室更方便与其沟通:https://gitter.im/python-trio/general。

准备工作

  • 确保你的python版本在3.5以及以上。
  • 安装trio。python3 -m pip install --upgrade trio
  • import trio 运行是否有错误,没有错误可以往下进行了。

知识准备Async方法

使用trio也就意味着你需要一直写异步方法。

# 一个标准方法
def regular_double(x):
return 2 * x # 一个异步方法
async def async_double(x):
return 2 * x

从外观上看异步方法和标准方法没什么区别只是前面多了个async。

“Async” 是“asynchronous”的简写,为了区别于异步函数,我们称标准函数为同步函数,

从用户角度异步函数和同步函数有以下区别:

  1. 要调用异步函数,必须使用await关键字。 因此,不要写regular_double(3),而是写await async_double(3).
  2. 不能在同步函数里使用await,否则会出错。

    句法错误:
def print_double(x):
print(await async_double(x)) # <-- SyntaxError here

但是在异步函数中,await是允许的:

async def print_double(x):
print(await async_double(x)) # <-- OK!

综上所述:作为一个用户,异步函数相对于常规函数的全部优势在于异步函数具有超能力:它们可以调用其他异步函数。

在异步函数中可以调用其他异步函数,但是凡事有始有终,第一个异步函数如何调用呢?

我们继续往下看

为何调用第一个异步函数

import trio

async def async_double(x):
return 2 * x trio.run(async_double, 3) # returns 6

这里我们可以使用trio.run来调用第一个异步函数。

接下来让我们看看trio的其他功能

异步中的等待

import trio

async def double_sleep(x):
await trio.sleep(2 * x) trio.run(double_sleep, 3) # does nothing for 6 seconds then returns

这里使用了异步等待函数 trio.sleep,它的功能和同步函数中的time.sleep()差不多,但是因为需要使用await调用,所以由前面的结论我们知道这是一个异步函数用的等待方法。

事实这个例子没有实际用处,我们用同步函数就可以实现这个简单的功能。这里主要是为了演示异步函数中通过await可以调用其他的异步函数。

异步函数调用的典型结构

trio.run -> [async function] -> ... -> [async function] -> trio.whatever

不要忘了写await

如果忘了写await会发生什么,我们看下面的这个例子

import time
import trio async def broken_double_sleep(x):
print("*yawn* Going to sleep")
start_time = time.perf_counter() # 糟糕,我忘了写await
trio.sleep(2 * x) sleep_time = time.perf_counter() - start_time
print("Woke up after {:.2f} seconds, feeling well rested!".format(sleep_time)) trio.run(broken_double_sleep, 3)

运行之后发现

*yawn* Going to sleep
Woke up after 0.00 seconds, feeling well rested!
__main__:4: RuntimeWarning: coroutine 'sleep' was never awaited

报错了,错误类型是RuntimeWarning,后面是说协程sleep没有使用await。

我们打印下trio.sleep(3)看到如下内容<coroutine object sleep at 0x7f5ac77be6d0>,表示这是一个协程,也就是一个异步函数由前面的内容可知。

我们把上面的trio.sleep(2 * x)改为await trio.sleep(2 * x)即可。

记住如果运行时警告:coroutine 'RuntimeWarning: coroutine '...' was never awaited',也就意味这有个地方你没有写await。

运行多个异步函数

如果trio只是使用await trio.sleep这样毫无意义的例子就没有什么价值,所以下面我们来trio的其他功能,运行多个异步函数。

# tasks-intro.py

import trio

async def child1():
print(" child1: started! sleeping now...")
await trio.sleep(1)
print(" child1: exiting!") async def child2():
print(" child2: started! sleeping now...")
await trio.sleep(1)
print(" child2: exiting!") async def parent():
print("parent: started!")
async with trio.open_nursery() as nursery:
print("parent: spawning child1...")
nursery.start_soon(child1) print("parent: spawning child2...")
nursery.start_soon(child2) print("parent: waiting for children to finish...")
# -- we exit the nursery block here --
print("parent: all done!") trio.run(parent)

内容比较多让我们一步一步分析,首先是定义了child1和child2两个异步函数,定义方法和我们上面说的差不多。

async def child1():
print("child1: started! sleeping now...")
await trio.sleep(1)
print("child1: exiting!") async def child2():
print("child2: started! sleeping now...")
await trio.sleep(1)
print("child2: exiting!")

接下来,我们将parent定义为一个异步函数,它将同时调用child1和child2

async def parent():
print("parent: started!")
async with trio.open_nursery() as nursery:
print("parent: spawning child1...")
nursery.start_soon(child1) print("parent: spawning child2...")
nursery.start_soon(child2) print("parent: waiting for children to finish...")
# 到这里我们调用__aexit__,等待child1和child2运行完毕
print("parent: all done!")

它通过使用神秘的async with语句来创建“nursery”,然后将child1和child2通过nusery方法的start_soon添加到nursery中。

下面我们来说说async with,其实也很简单,我们知道再读文件时候我们使用with open()...去创建一个文件句柄,with里面牵扯到两个魔法函数

在代码块开始的时候调用_enter_()结束时再去调用_exit_()我们称open()为上下文管理器。async with someobj语句和with差不多只不过它调用的异步方法的魔法函数:_aenter__和_aexit。我们称someobj为“异步上下文管理器”。

再回到上面的代码首先我们使用async with创建一个异步代码块

同时通过nursery.start_soon(child1)和nursery.start_soon(child2)

调用child1和child2函数开始运行然后立即返回,这两个异步函数留在后台继续运行。

然后等待child1和child2运行结束之后,结束async with代码块里的内容,打印最后的

"parent: all done!"。

让我们看看运行结果

parent: started!
parent: spawning child1...
parent: spawning child2...
parent: waiting for children to finish...
child2: started! sleeping now...
child1: started! sleeping now...
[... 1 second passes ...]
child1: exiting!
child2: exiting!
parent: all done!

可以发现和我们上面分析的一样。看到这里,如果你熟悉线程的话,你会发现这个运作机制和多线程类似。但是这里并不是线程,这里的代码全部在一个线程里面的完成,为了区别线程我们称这里的child1和child2为两个任务,有了任务,我们只能在某些我们称之为“checkpoints”的指定地点进行切换。后面我们再深挖掘它。

trio里的跟踪器

我们知道上面的多个任务都是在一个线程中进行切换操作的,但是对于如何切换的我们并不了解,只有知道了这些我们才能更好的学好一个模块。

幸运的是,trio提供了一组用于检查和调试程序的工具。我们可以通过编写一个Tracer类,来实现trio.abc.Instrumen接口。代码如下

class Tracer(trio.abc.Instrument):
def before_run(self):
print("!!! run started") def _print_with_task(self, msg, task):
# repr(task) is perhaps more useful than task.name in general,
# but in context of a tutorial the extra noise is unhelpful.
print("{}: {}".format(msg, task.name)) def task_spawned(self, task):
self._print_with_task("### new task spawned", task) def task_scheduled(self, task):
self._print_with_task("### task scheduled", task) def before_task_step(self, task):
self._print_with_task(">>> about to run one step of task", task) def after_task_step(self, task):
self._print_with_task("<<< task step finished", task) def task_exited(self, task):
self._print_with_task("### task exited", task) def before_io_wait(self, timeout):
if timeout:
print("### waiting for I/O for up to {} seconds".format(timeout))
else:
print("### doing a quick check for I/O")
self._sleep_time = trio.current_time() def after_io_wait(self, timeout):
duration = trio.current_time() - self._sleep_time
print("### finished I/O check (took {} seconds)".format(duration)) def after_run(self):
print("!!! run finished")

然后我们运行之前的示例但是这次我们传入的是一个Tracer对象。

trio.run(parent, instruments=[Tracer()])

然后我们会发现打印了一大堆东西下面我们一部分一部分分析。

!!! run started
### new task spawned: <init>
### task scheduled: <init>
### doing a quick check for I/O
### finished I/O check (took 1.787799919839017e-05 seconds)
>>> about to run one step of task: <init>
### new task spawned: __main__.parent
### task scheduled: __main__.parent
### new task spawned: <TrioToken.run_sync_soon task>
### task scheduled: <TrioToken.run_sync_soon task>
<<< task step finished: <init>
### doing a quick check for I/O
### finished I/O check (took 1.704399983282201e-05 seconds)

前面一大堆的信息我们不用去关心,我们看### new task spawned: _main_.parent,可知_main_.parent创建了一个任务。

一旦初始的管理工作完成,trio就开始运行parent函数,您可以看到parent函数创建了两个子任务。然后,它以块的形式到达异步的末尾,并暂停。

>>> about to run one step of task: __main__.parent
parent: started!
parent: spawning child1...
### new task spawned: __main__.child1
### task scheduled: __main__.child1
parent: spawning child2...
### new task spawned: __main__.child2
### task scheduled: __main__.child2
parent: waiting for children to finish...
<<< task step finished: __main__.parent

然后到了trio.run(),记录了更多的内部运作过程。

>>> about to run one step of task: <call soon task>
<<< task step finished: <call soon task>
### doing a quick check for I/O
### finished I/O check (took 5.476875230669975e-06 seconds)

然后给这两个子任务一个运行的机会

>>> about to run one step of task: __main__.child2
child2 started! sleeping now...
<<< task step finished: __main__.child2 >>> about to run one step of task: __main__.child1
child1: started! sleeping now...
<<< task step finished: __main__.child1

每个任务都在运行,直到调用trio.sleep()然后突然我们又回到trio.run ()决定下一步要运行什么。这是怎么回事?秘密在于trio.run ()和trio.sleep ()一起实现的,trio.sleep()可以获得一些特殊的魔力,让它暂停整个调用堆栈,所以它会向trio.run ()发送一个通知,请求在1秒后再次被唤醒,然后暂停任务。任务暂停后,Python将控制权交还给trio.run (),由它决定下一步要做什么。

注意:在trio中不能使用asyncio.sleep()。

接下来它调用一个操作系统原语来使整个进程进入休眠状态

### waiting for I/O for up to 0.9997810370005027 seconds

1s休眠结束后

### finished I/O check (took 1.0006483688484877 seconds)
### task scheduled: __main__.child1
### task scheduled: __main__.child2

还记得parent是如何的等待两个子任务结束的么,下面注意观察child1退出的时候

parent在干什么

>>> about to run one step of task: __main__.child1
child1: exiting!
### task scheduled: __main__.parent
### task exited: __main__.child1
<<< task step finished: __main__.child1 >>> about to run one step of task: __main__.child2
child2 exiting!
### task exited: __main__.child2
<<< task step finished: __main__.child2

然后先进行io操作,然后parent任务结束

### doing a quick check for I/O
### finished I/O check (took 9.045004844665527e-06 seconds) >>> about to run one step of task: __main__.parent
parent: all done!
### task scheduled: <init>
### task exited: __main__.parent
<<< task step finished: __main__.parent

最后进行一些内部操作代码结束

### doing a quick check for I/O
### finished I/O check (took 5.996786057949066e-06 seconds)
>>> about to run one step of task: <init>
### task scheduled: <call soon task>
### task scheduled: <init>
<<< task step finished: <init>
### doing a quick check for I/O
### finished I/O check (took 6.258022040128708e-06 seconds)
>>> about to run one step of task: <call soon task>
### task exited: <call soon task>
<<< task step finished: <call soon task>
>>> about to run one step of task: <init>
### task exited: <init>
<<< task step finished: <init>
!!! run finished

ok,这一部分只要说了运作机制了解即可,当然记住更方便对trio的理解。

关于更多的trio的使用,敬请期待。。。

参考资料

https://trio.readthedocs.io/en/latest/tutorial.html#before-you-begin

初识python异步模块Trio的更多相关文章

  1. 初识python: 模块定义及调用

    一.定义 模块:用来从逻辑上组织python代码(变量.函数.类.逻辑:实现一个功能),本质就是.py结尾的python文件(比如:文件名:test.py,对应的模块名:test) 包:用来从逻辑上组 ...

  2. 孤荷凌寒自学python第二十七天python的datetime模块及初识datetime.date模块

    孤荷凌寒自学python第二十七天python的datetime模块及初识datetime.date模块 (完整学习过程屏幕记录视频地址在文末,手写笔记在文末) 一.datetime模块 dateti ...

  3. Python开发【第一篇】:初识Python

    初识python 一.python简介 python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解 ...

  4. Python开发【第二篇】:初识Python

    Python开发[第二篇]:初识Python   Python简介 Python前世今生 python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏 ...

  5. Python异步IO --- 轻松管理10k+并发连接

    前言   异步操作在计算机软硬件体系中是一个普遍概念,根源在于参与协作的各实体处理速度上有明显差异.软件开发中遇到的多数情况是CPU与IO的速度不匹配,所以异步IO存在于各种编程框架中,客户端比如浏览 ...

  6. python 各模块

    01 关于本书 02 代码约定 03 关于例子 04 如何联系我们 1 核心模块 11 介绍 111 内建函数和异常 112 操作系统接口模块 113 类型支持模块 114 正则表达式 115 语言支 ...

  7. 深入理解 Python 异步编程(上)

    http://python.jobbole.com/88291/ 前言 很多朋友对异步编程都处于"听说很强大"的认知状态.鲜有在生产项目中使用它.而使用它的同学,则大多数都停留在知 ...

  8. Thread类的其他方法,同步锁,死锁与递归锁,信号量,事件,条件,定时器,队列,Python标准模块--concurrent.futures

    参考博客: https://www.cnblogs.com/xiao987334176/p/9046028.html 线程简述 什么是线程?线程是cpu调度的最小单位进程是资源分配的最小单位 进程和线 ...

  9. python 全栈开发,Day42(Thread类的其他方法,同步锁,死锁与递归锁,信号量,事件,条件,定时器,队列,Python标准模块--concurrent.futures)

    昨日内容回顾 线程什么是线程?线程是cpu调度的最小单位进程是资源分配的最小单位 进程和线程是什么关系? 线程是在进程中的 一个执行单位 多进程 本质上开启的这个进程里就有一个线程 多线程 单纯的在当 ...

随机推荐

  1. Java 读取propertoes文件

    我一直不懂,Java里面的路径是咋样的,怎么找到我的资源文件? 直到我看到了这篇文件,写的是真棒.这篇文章  看完之后,豁然开朗的感觉 下面做些笔记,首先,Java有一个targer文件,下面有一个c ...

  2. Hadoop记录-Hadoop集群添加节点和删除节点

    1.添加节点 A:新节点中添加账户,设置无密码登陆 B:Name节点中设置到新节点的无密码登陆 C:在Name节点slaves文件中添加新节点 D:在所有节点/etc/hosts文件中增加新节点(所有 ...

  3. 解决 git push Failed to connect to 127.0.0.1 port 8-87: 拒绝连接

    今天在本地使用nsq 测试的时候总是提示端口被占用 通过查看环境变量确实存在该代理 如何解决 使用netstat 命令查看端口被占用情况 根据经常ID号查看是哪一个进程正在被占用 如何还是不行,则在[ ...

  4. Golang入门教程(十一)beego 框架之RESTful Controller 路由

    官方文档:https://beego.me/docs/mvc/controller/router.md 什么是路由设置呢?前面介绍的 MVC 结构执行时,介绍过 beego 存在三种方式的路由:固定路 ...

  5. Spark源码解析 - Spark-shell浅析

    1.准备工作 1.1 安装spark,并配置spark-env.sh 使用spark-shell前需要安装spark,详情可以参考http://www.cnblogs.com/swordfall/p/ ...

  6. solr插件导入数据库中的数据

    solr插件导入数据库中的数据 1:自定义与数据库对应的域: 1.1. 设置业务系统Field 如果不使用Solr提供的Field可以针对具体的业务需要自定义一套Field. 例如:如下是商品信息Fi ...

  7. 八、文件IO——存储映射

    8.1 存储映射介绍 8.1.1 概念 存储映射是一个磁盘文件与存储空间的一个缓存相映射,对缓存数据的读写就相应的完成了文件的读写. 文件操作部分映射到虚拟内存的一块区域,我们对虚拟内存映射的那块区域 ...

  8. MVC中的分部视图

    背景: 项目的工期马上就要到了,由于后台封装的很好,我们只需要用心熟悉框架,接下来后台的工作就是简单的代码工作了.原本以为最困难的时期已经过去,可没想到前台才是最困难的. B/S的基础十分薄弱,加上B ...

  9. 梯度下降算法对比(批量下降/随机下降/mini-batch)

    大规模机器学习: 线性回归的梯度下降算法:Batch gradient descent(每次更新使用全部的训练样本) 批量梯度下降算法(Batch gradient descent): 每计算一次梯度 ...

  10. Coursera Deep Learning 2 Improving Deep Neural Networks: Hyperparameter tuning, Regularization and Optimization - week2, Optimization algorithms

    Gradient descent Batch Gradient Decent, Mini-batch gradient descent, Stochastic gradient descent 还有很 ...