分享一下完全不依赖 asyncio 也支持异步语法的库

2021-09-07 10:15:43 +08:00
 sujin190

https://github.com/snower/sevent

异步语法的支持完全不依赖 asyncio,当然并没有说可以替代 asyncio 或者更好啥的,只是一种实现,如果有对异步 io 或者 python 异步语法实现感兴趣的可以一看吧。

只要是用于代理流量转发这样的场景,所以接口毕竟简单,当然支持范围也就没 asyncio 那么广了,从 echo 测试来看,性能还是要好于 asyncio 一些的,helpers 中也简单实现了几个工具。

HTTP 请求测试

import sevent

async def http_test():
    s = sevent.tcp.Socket()
    await s.connectof(('www.baidu.com', 80))
    await s.send(b'GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: Close\r\nUser-Agent: curl/7.58.0\r\nAccept: */*\r\n\r\n')

    data = b''
    while True:
        try:
            data += (await s.recv()).read()
        except sevent.tcp.SocketClosed:
            break
    print(data.decode("utf-8"))
    await s.closeof()

sevent.run( http_test)

TCP 端口转发

import sys
import sevent

async def tcp_port_forward_server():
    server = sevent.tcp.Server()
    server.listen(("0.0.0.0", int(sys.argv[1])))

    while True:
        conn = await server.accept()
        pconn = sevent.tcp.Socket()
        pconn.connect((sys.argv[2], int(sys.argv[3])))
        conn.link(pconn)

sevent.run(tcp_port_forward_server)
4495 次点击
所在节点    Python
59 条回复
wamson
2021-09-07 15:25:50 +08:00
正好对这个有兴趣,话说这个有什么比较有名的方案吗?比如底层是 c++,io_loop 是 lib_event,通过 pybind11 来驱动 python 脚本层级,那么 python 层该怎么使用 async 语法糖呢。眼馋 python 的 async 语法糖好久了。
haoliang
2021-09-07 15:56:43 +08:00
没有文档,粗略爬过代码;我的理解中,`sevent.go(callback)` 对应 `asyncio.create_task`, 在 sevent 中如何等待这个 callback 的完成呢? callback 运行过程中抛错,该如何处理?
sujin190
2021-09-07 16:08:23 +08:00
@wamson #21 async 语法糖本质就是一个迭代器,用迭代器的方法驱动运行就好了,搞 c++的话,去瞅一下 asyncio 的源码很快就知道了吧,而且你可以把 asyncio 的那部分源码搬过来放到你的 c++代码里啊
sujin190
2021-09-07 16:10:12 +08:00
@haoliang #22 只是一看,没啥动力写文档啊,callback 出错只是单纯用 logging 输出调用栈信息了,其实一般来说如果你关注 callback 出的错,那么你应该在一进入 callback 时就自己加 try 就是了呀
sujin190
2021-09-07 16:10:23 +08:00
@tmac010sjh #20 跑人了
ysc3839
2021-09-07 17:03:00 +08:00
@sujin190 有用的,主要就是为了避免回调地狱。
sujin190
2021-09-07 17:16:37 +08:00
@ysc3839 #26 没有底层的 asyncio 的 ioloop,协程之间就缺少切换点了,难道你想用线程池配合 await 使用?可是多线程模式下 Lock 、Event 、Semaphore 就是用来解决你遇到的回调问题,并不需要多次一举强行又别扭的使用 await 啊

await 本质就是个状态管理器,他也是注册 callback,只不过这个 callback 用来触发协程切换了,但是线程调度由操作系统来管理,所以这不又回到 Lock 、Event 、Semaphore 了
ysc3839
2021-09-07 17:32:29 +08:00
@sujin190 我就是为了用 async function 代替回调函数,并不是要用什么线程池,整个程序是单线程的,也不希望搞成多线程的模式。
这也是我不能理解 Python 协程的地方,别的语言如 C++, JavaScript 的协程就是回调函数的语法糖,但 Python 好像要有个调度器才能跑。
sujin190
2021-09-07 17:44:16 +08:00
@ysc3839 #28 JavaScript 语言直接自带调度器,你都不能不要,不知道你用的 C++的库是啥,其实你这个想法才挺奇怪,async await 语法本来就是 A 等待 B 的一个结果,本身就代表有两个独立调用栈,没用调度器,直接一个单一线程如何能驱动两个独立调用栈运行呢
ysc3839
2021-09-07 17:48:31 +08:00
@sujin190 但是 JavaScript 的调度器跟它的协程关系并不大,调度器执行的是回调函数,并不跟协程深度绑定。
C++ 说的是 C++20 内置的协程。
没有调度器,也能直接通过回调函数来驱动协程执行。比如 C++ 的协程在 await 的时候,被 await 的对象能拿到协程的“回调函数”,后续执行这个回调函数就是恢复协程执行了。
sujin190
2021-09-07 18:09:25 +08:00
@ysc3839 #30 我知道你说的是啥,其实就是 future 对象吧,需要传 callback 的地方你可以构造一个 future 对象传过去,future 的 set_result 就是你说的 c++中的那个能拿到协程的“回调函数”了,Javascript 的异步函数返回时一个 Promise 对象,换言之,你需要传 callback 也可以改成传 Promise 对象,Javascript 不需要显示把协程放在调度器中运行时因为自动在全局包了一个调度器函数,其实底层和 python 是一样的
sujin190
2021-09-07 18:20:21 +08:00
@ysc3839 #30 关于 C++协程据我之前看过几个库的实现来看,c++作为底层语言,自身就能够通过操作内存结构和长 jmp 指令改变调用栈,所以基于此是可以无需调度器就可以完成协程切换的,但是无论 Javascript 、python 、ruby 、php 这样的语言可是都做不到手动修改栈帧这样的操作,这点和 c++这样的更底层的语言还是很大不同的,Javascript 看起来有点像,也只是解释器会隐式加很多东西
ysc3839
2021-09-07 19:01:32 +08:00
我说的“协程的回调函数”不是 future 或者 JS 异步函数返回的 Promise,而是用于恢复协程执行的东西,类似 Promise 里面的 resolve 函数。
我说的 C++ 协程不是第三方实现的有栈协程,而是语言本身实现的无栈协程,这种协程不依赖具体硬件平台,而是依赖编译器实现。
可以看一下用 Babel 转换过的协程代码,或者反编译看看 C++ 协程编译后的代码,其实就是在 await 的地方拆分开。

用伪代码举个例子:
原始代码:
```
async function test() {
func_a();
await func_b();
await func_c();
func_d();
}

test();
```

处理后的代码:
```
function test(state) {
switch(state.step) {
case 0:
state.step = 1;
func_a();
func_b(test, state);
break;
case 1:
state.step = 2;
func_c(test, state);
break;
case 2:
func_d();
break;
}
}

let state = {step: 0};
test(state);
```

此时 func_b 或者 func_c 要恢复协程执行的话,只需要调用 test(state); 即可,这样可以轻松对接那些使用回调函数的库,避免回调地狱。

印象中 Python 也是类似这种模式的协程,按理说可以直接当回调函数用,但是实际上却捆绑了调度器,反而丧失了灵活性。
就像前面 @wamson 所说的,很多情况下是已经有了一个现成的使用回调函数的事件循环,想要使用 async await 的语法简化代码,这种情况下 C++ 或 JS 的协程很容易就能对接,而 Python 就做不到了。
joApioVVx4M4X6Rf
2021-09-07 19:31:12 +08:00
牛逼啊,好久没在 python 区看见这种高端技术贴了,求多发点
sujin190
2021-09-07 19:44:42 +08:00
@ysc3839 #33 你这是把 c++和 JavaScript es5 的实现想完全搬到 python 上来啊,但事实上这两都是没有更好方法的时候不得已才这样用的,python 的实现更简单,用 future 对象把那些用 callback 的库包装一下就行了

```
def func(callback):
callback()

def func_await():
futuer = Futuer()
def on_finish():
futuer.set_result()
a(on_finish)
return futuer

async def test():
func1()
await func_await()
await func_await2()
func2()
```

大概就是这样样子就好了
ysc3839
2021-09-08 10:27:06 +08:00
@sujin190 自己写了段测试代码,但是运行时出错了
https://gist.github.com/ysc3839/4445c6c2bc1e0ce02790a52fed0c8b38
错误信息是:
test.py:20: RuntimeWarning: coroutine 'test' was never awaited
test()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
test.py:21: RuntimeWarning: coroutine 'test' was never awaited
test()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
ysc3839
2021-09-08 10:48:41 +08:00
@ysc3839 顺便写了个用 C++ 协程实现的 https://godbolt.org/z/sGnzTnvjr
sujin190
2021-09-08 10:54:52 +08:00
@ysc3839 #36 https://gist.github.com/snower/c4f1d4d55281e9456daf1a66831db821

async 函数调用后其实返回了一个 coroutine 对象,python 默认不会启动调度器,所以默认并不会运行这个 coroutine 对象,JavaScript 可以,那是因为 JavaScript 默认就启动了调度器,全局定义为被执行的 Promise 都会默认被直接执行,这两个没啥区别

当然 python async 函数调用返回 coroutine 使用 send 函数就能手动运行起来了,但是注意 asyncio 的 Future 调用 set_result 触发 await 返回其实是又被放到当前 asyncio loop 中去执行了,所以你还是要用 asyncio 创建 loop 来运行

我看 c++ 20 的协程也是需要调度器的吧
sujin190
2021-09-08 11:26:46 +08:00
@ysc3839 #37 静态语言改编译器,改内存布局能,直接长 jmp 就是牛逼,这是在编译时直接把 test 函数拆成几个了啊
ysc3839
2021-09-08 13:00:19 +08:00
@sujin190 C++20 的协程不需要调度器,你可以看看我前面的代码。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/800308

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX