[百度分享]用户态线程库(3)解决思路

[百度分享]用户态线程库(3)
更轻量的线程 
严格说来, 异步化的事件驱动方式出现的比线程模式更早, 线程的出现本身就是为了减轻复杂的状态机编程。 在一般情况下确实给我们的程序带来了便利. 当时另一方面也带来了不少麻烦 
锁的应用, 需要小心死锁等问题. 另外互斥情况下的性能开销也是不可忽视的 
共享数据的修改和读取很容易出现问题, 而且不好排查,即使有了valgrind 这样工具,很多问题依然是很容易出现 
但另一方面, 这些问题对于上面的提到异步方式也是同样存在的, 本质上还是存在线程间的互斥问题。不过对于这种模式可以采用单进程的方式的来规避其中的一些问题, 但在利用多CPU的问题上还是需要另外的考虑 
前面提到过多线程的本质是记录环境的上下文,保存CPU的状态。 在glibc中提供了makecontext, swapcontext的方式来记录这些信息, 利用这两个调用,我们可以在用户态上实现轻量级的线程库。 
makecontext 创建一个新的上下文,可以传入我们自己定义的栈空间。它记录CPU的各种信息 
swapcontext 切换上下文,其实就是改变CPU的相关寄存器 状态,替换到另外的可执行的上下文中 

一个简单例子: 
 一个简单例子: 
 
C/C++ code
 void call_thread(uint32_t p1, uint32_t p2) 
 { 
     ucontext_t* ctx = (ucontext_t*)((uint64_t)p2 | ((uint64_t)p1) << 32); 
     ucontext_t u; 
     //。。。运行线程 
     swapcontext(&u, ctx); 
 } 
  
 void thread_mgr()
 {
     //设置上下文
     
     ucontext_t ctx, u;
     //初始化 u
     u.uc_stack.ss_sp = (char*)stack_buff + pagesize;
     u.uc_stack.ss_size = thread_stack_size;
     u.uc_stack.ss_flags = 0;
     uint32_t p1, p2;
     //低版本glibc实现问题不支持64bit指针
     p1 = (uint32_t)((0x00000000FFFFFFFF)&((uint64_t)(&ctx))&gt;&gt;32); //高位
     p2 = (uint32_t)(0x00000000FFFFFFFF&(uint64_t)(&ctx)); //低位
     makecontext(&u, (void(*)(void))(&call_thread), 2, p1, p2);
     swapcontext(&ctx, &u); //记住当前位置,切换到u的位置上, 在 call_thread 中swapcontext切换回来
     
     ...
 }

通过这样的切换方式我们可以在单线程程序中简单实现一个轻量级的线程库,由线程库负责在可用的调度实体上调度用户线程。这会使得线程上下文切换非常的快,这主要避免了系统调用。在测试中切换的性能是pthread库的切换(从pthread_create到线程运行线程 与 makecontext后的比较) 的10倍以上. 
事实上这是m * n 的线程库的基本实现方式, 不过这里存在几个问题 
单线程运行不能利用多CPU, 需要以来pthread或者clone的多线程实现 或者干脆利用多CPU 
调度必须自己考虑, 没有外部来进行线程的切换。 
一个轻线程出现死锁,或者需要长时间等待操作的时候会影响一批线程 
不过对于这些,我们可以在yield()主动让出CPU的语义上进行扩展, 当轻线程调用到yield(), 就主动进行切换,让另外的轻线程运行。 通过在这个层面上的封装,我们可以实现各种不同的调度方式,比如发起IO操作,非堵塞方式处理没有完全处理完毕,就切换,如果处理完毕就继续运行。 
记录当前的栈状态, 切换出去,然后再切回来, 整个流程类似下面: 
 
C/C++ code
 class task 
 { 
     public: 
     virtual int run() { 
         //... 
         //切换, 监控句柄 放入pool中 
     } 
 } 
 1
 //epoll_wait 等待句柄激活
 //激活的调度
 if (fd 激活) {
     //上下文件切换到 fd对应的地方运行

将所有的切换都采用网络句柄或者管道(纯CPU采用管道封装,统一化),这点上和前面的异步事件模型采用类似的方式, 都是通过事件方式进行激活 
事实上这种方式在erlang, ruby等语言中也都有相应的实现, 基本原理就如上面所述,每个运行实例都维护着自己的上下文和栈空间. 这种线程另一种叫法是协程(Coroutine), 本质上是属于一种非抢占式线程 
注:在具体线程实现方式上,也可以利用longjump来实现,性能上来说更好的, 不过这里对于各种状态需要自己记录,并且还需要考虑CPU缓存等问题相对问题比较(全部考虑完就和makecontext差不多了)。所以这里采用的是makecontext的方式。
可控制的异步调用 
在用户态的线程库中,我们可以采用同步思维方式写出等价于异步效果的程序,并且在性能提升方面占有一定的优势. 
一方面可以付出一定的内存开销(异步条件上下文完全通过内存池维护空间会小一些)开辟出更多的线程(几十W级别规模), 另一方面编程模式不会发生太大的改变 
计算异步化 
这里主要是考虑,类似 GPU或者数据压缩卡这样的计算模式, 将一部分的计算拿出去进行计算, CPU可以继续进行其他工作,从而短时间内处理更多的工作。

------解决方案--------------------
继续支持,谢谢LZ分享
------解决方案--------------------
接分..
------解决方案--------------------
紧急问大家一个问题:
大家好,请教一个问题:
1 绘制图案
如下图:
外面是个正方形;里面分别以四个边位直径,画圆!
图形数学描述为:
1)图案整体轮廓为正方形;
2)以四个边为直径,在正方形内部作半圆;
3)四个半圆相交部分构成黑色填充的类似花瓣的区域。
假如:
1)我们使用数组来表示一个图形;
2)数组中的每个元素对应一个图形中一个点;
3)对于一个点,元素值为0表示白色,1表示黑色;
4)正方形的一边边长为 d。
编程要求:
1)写一函数,使用数组表示上图的图案;
2)该函数的结果,将图形结果表示到屏幕当中。
------解决方案--------------------
快乐接分...