[百度分享]用户态线程库(3)
更轻量的线程
严格说来, 异步化的事件驱动方式出现的比线程模式更早, 线程的出现本身就是为了减轻复杂的状态机编程。 在一般情况下确实给我们的程序带来了便利. 当时另一方面也带来了不少麻烦
锁的应用, 需要小心死锁等问题. 另外互斥情况下的性能开销也是不可忽视的
共享数据的修改和读取很容易出现问题, 而且不好排查,即使有了valgrind 这样工具,很多问题依然是很容易出现
但另一方面, 这些问题对于上面的提到异步方式也是同样存在的, 本质上还是存在线程间的互斥问题。不过对于这种模式可以采用单进程的方式的来规避其中的一些问题, 但在利用多CPU的问题上还是需要另外的考虑
前面提到过多线程的本质是记录环境的上下文,保存CPU的状态。 在glibc中提供了makecontext, swapcontext的方式来记录这些信息, 利用这两个调用,我们可以在用户态上实现轻量级的线程库。
makecontext 创建一个新的上下文,可以传入我们自己定义的栈空间。它记录CPU的各种信息
swapcontext 切换上下文,其实就是改变CPU的相关寄存器 状态,替换到另外的可执行的上下文中
一个简单例子:
一个简单例子:
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))>>32); //高位 p2 = (uint32_t)(0x00000000FFFFFFFF&(uint64_t)(&ctx)); //低位 makecontext(&u, (void(*)(void))(&call_thread), 2, p1, p2); swapcontext(&ctx, &u); //记住当前位置,切换到u的位置上, 在 call_thread 中swapcontext切换回来 ... } class task { public: virtual int run() { //... //切换, 监控句柄 放入pool中 } } 1 //epoll_wait 等待句柄激活 //激活的调度 if (fd 激活) { //上下文件切换到 fd对应的地方运行