• coroutine


    在脚本语言中,coroutine 不是个新鲜词汇,比如 lua 内建 coroutine,python中的greenlet,但在C程序中,并不是太常见。

    windows 下有 fiber,相关函数为

        ConvertThreadToFiber()  thread 转 fiber
        CreateFiber() 创建 fiber

     
    unix 下没有对应语义,但可以通过 setcontext() 函数来实现对应的功能。

        setcontext() 早期是 POSIX 规范中的一部分,但在 POSIX.1-2008 已经移除了这个函数。
        setcontext() 说白了,就是将当前线程的处理器的寄存器的值作个保存,并切换寄存器值为目标环境。
         

    unix下比较出名的实现有:
     
    GNU Pth - The GNU Portable Threads。它是GNU在不支持线程的unix系统上提供 pthread 的 API,IO函数都基于select实现的,并用 select 模拟了 poll 函数。
    函数是 pth_ 前缀的,有线程相关API 和 fork,select,read,write 等函数。同时在需要的情况下(configure选项 --enable-syscall-soft)会用宏替换fork,select,read,write 等函数,并提供自己 pthread 风格的API(configure选项 --enable-pthread)。
     
    libtask(FreeBSD, Linux, OS X, and Solaris)。并对一些必须的IO函数作了封装,IO函数都是基于poll实现的。但不提供替换pthread,fork,select。但提供了 printf 函数家族。其实现和提供的 api 相比 pth 更小巧。
     
     
    coroutine的优点:
    1) 阻塞式的代码逻辑(不需要状态机,各种回调函数),达到非阻塞式编程的效果(减少 thread 数量)。
     
     
    coroutine的缺点:
    1) 不能有效利用SMP;
    2) 非抢占式,会导致各个 coroutine 之间不能公平调度CPU资源;
    3) 调试上不方便——各大调试工具都可以方便的查看每个thread的堆栈,及函数变量,但coroutine下你看不了。
     
     
     
    使用场景:
    1) coroutine 用在对遗留代码的复用上。对IO函数和创建线程的函数作些修改就可以用上 coroutine,程序成功转换为 IO multiplexing
    2) 线程受限的情况(在脚本语言的世界很普遍)。比如python,解释器受GIL的限制,导致解释器不能真正发挥多线程的威力,这时 coroutine 就能有效提高系统 IO 负荷。就象我们看到的webpy用上gevent后,性能提升很大,特别是在有大量空闲连接时。
    3) coroutine 并不适合用来运行 CPU 密集型的task。因为 CPU 密集型task会一直占用thread,导致 IO 的 poll 得不到及时运行,其它task很难有机会被调度,这样拖慢了所有task的响应速度。如果存在这样的 task,请将它移到单独的 thread 上运行。

    改造实践:

    这里以 xrdp 为例,

    1)pth hard 模式替换IO函数,替换 pthread (链接pth的pthread库)

    禁用SSL连接的情况下,可以正常工作; 启用则 SSL_accept 总是返回-1 。抓包分析,client 已经回复 Client Key Exchange, Change Cipher Spec, Finished

    2)pth hard 模式替换IO函数,不替换 pthread

    调用 patch 过的 select 函数时出现段错误。原因,初始化连接的时候的 rfx_context_new() 会创建 thread pool,而 thread pool 中的线程在调用 select 的时候,会被重定向为 pth_select 函数,最后在 pth_key_getdata() 处出现断错误。

    3)pth soft 模式替换IO函数,不替换 pthread

    禁用SSL连接的情况下,可以正常工作; 启用则 SSL_accept 总是返回-1 。抓包分析,client 已经回复 Client Key Exchange, Change Cipher Spec, Finished

    这里我们看到方案2是问题最大的,原生thread 和 hard 模式IO函数相冲突。方案1,3都是因为OpenSSL不能再coroutine中正常工作。

    个人经验:1)如果程序很简单,没用到任何第三方库,那直接用方案1,即替换IO和pthread。2)如果程序使用到了第三方库,那就是用方案3,并小心的限制coroutine的代码都在主线程中运行,coroutine 中的代码用上 pth 的 IO 函数。不用替换IO了,也别用 pth 的 pthread 库,原因都是为了保证第三方库的正常运转。hard模式替换的IO函数,看其实现并没有考虑多线程的问题,所以在多 thread 环境中使用有问题;更要命是,hard模式替换函数,有些并不能正确找到原型,比如在RHEL6 x64上 waitpid sigprocmsak 等。3)如果不考虑用 pth 的模拟层,新代码更建议使用简明的 libtask。

    注:

    GNU Pth 编译为syscall hard模式时候,RHEL6 x64编译后运行会报找不到 sigprocmask 系统调用,增加下面两行:

    intern int pth_sc_sigprocmask(int how, const sigset_t *set, sigset_t *oset)
    {
        /* internal exit point for Pth */
        if (pth_syscall_fct_tab[PTH_SCF_sigprocmask].addr != NULL)
            return ((int (*)(int, const sigset_t *, sigset_t *))
                   pth_syscall_fct_tab[PTH_SCF_sigprocmask].addr)
                   (how, set, oset);
    #if defined(HAVE_SYSCALL) && defined(SYS___sigprocmask14) /* NetBSD */
        else return (int)syscall(SYS___sigprocmask14, how, set, oset);
    #elif defined(HAVE_SYSCALL) && defined(SYS_sigprocmask)
        else return (int)syscall(SYS_sigprocmask, how, set, oset);
    #elif defined(HAVE_SYSCALL) && defined(SYS_rt_sigprocmask)
        else return (int)syscall(SYS_rt_sigprocmask, how, set, oset);
    #else
        else PTH_SYSCALL_ERROR(-1, ENOSYS, "sigprocmask");
    #endif
    }
  • 相关阅读:
    promise!
    123
    git回忆回忆回忆
    Vue基本指令
    vue小案例(跑马灯)
    mvc
    nodejs中path模块
    web服务端重定向
    弹性布局
    导出数据库的表的所有字段类型,长度,名称
  • 原文地址:https://www.cnblogs.com/JesseFang/p/3374172.html
Copyright © 2020-2023  润新知