TGTGInsighttelegram intelligenceLIVE / telegram public index
← Welcome to the Black Parade
Welcome to the Black Parade avatar

TGINSIGHT POST

Post #21

@TheB1ackParade

Welcome to the Black Parade

Views123Post view count
PostedFeb 1702/17/2020, 02:43 PM
Post content

Post content

背到 setjmp / longjmp 的时候我想, 是不是用这两个 syscall 可以实现 coroutine 呢? 查了一下, 发现了一个废弃的了 syscall ucontext.h 系列: https://pubs.opengroup.org/onlinepubs/7908799/xsh/ucontext.h.html, 在 SUSv3 里还是标准, v4 就因为兼容性废弃了, 可惜啦. 简单看了一下, 非常简单好用, 比方说我要实现 greenlet 里最简单的示例: from greenlet import greenlet def test1(): print(12) gr2.switch() print(34) def test2(): print(56) gr1.switch() print(78) gr1 = greenlet(test1) gr2 = greenlet(test2) gr1.switch() 这段代码会打印 12\n56\n34\n, 而 78\n 是不会到达的, 具体的解释可以去看: https://greenlet.readthedocs.io/en/latest/ 那么这段代码用 ucontext.h 提供的 syscall 是很容易实现, 也很容易看懂: #include <ucontext.h> #include <stdio.h> #include <stdlib.h> #define handle_error(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0) static ucontext_t uctx_main, uctx_test1, uctx_test2; static void test1(void) { printf("%d\n", 12); if (swapcontext(&uctx_test1, &uctx_test2) == -1) handle_error("failed to swapcontext"); printf("%d\n", 34); } static void test2(void) { printf("%d\n", 56); if (swapcontext(&uctx_test2, &uctx_test1) == -1) handle_error("failed to swapcontent"); printf("%d\n", 78); } int main(int argc, char *argv[]) { char test1_stack[16384]; char test2_stack[16384]; if (getcontext(&uctx_test1) == -1) handle_error("failed to getcontext"); uctx_test1.uc_stack.ss_sp = test1_stack; uctx_test1.uc_stack.ss_size = sizeof(test1_stack); uctx_test1.uc_link = &uctx_main; // test1 returns and comes back to main makecontext(&uctx_test1, test1, 0); if (getcontext(&uctx_test2) == -1) handle_error("failed to getcontext"); uctx_test2.uc_stack.ss_sp = test2_stack; uctx_test2.uc_stack.ss_size = sizeof(test2_stack); // test2 returns and comes back to main uctx_test1.uc_link = &uctx_main; makecontext(&uctx_test2, test2, 0); if (swapcontext(&uctx_main, &uctx_test1) == -1) handle_error("failed to swapcontext"); return 0; } 就是代价行为, 代码也不别扭, 很正常的 coroutine 书写逻辑. 以此为基础, 要构建上层 API 就不是问题了, 首先是 Greenlet.switch(*args, **kwargs) -> Any 熟悉 Python yield 的同学应该知道这对应的是 coroutine.send(value)操作, 用 ucontext 实现起来没问题, 不赘述. 然后是高阶 API, 比如 Gevent 或者 Go 所提供的 用同步逻辑写 coroutine 的超能力, 只要对可 select 的阻塞调用包装一层就可以了, 属于 1->2 的功能. 这简直就是对我这种没学过汇编的 API 小子的福利啊, 我感觉自己又可以造协程轮子了, 逃!