乐趣区

关于c++:linux-c编程之高效线程池如何实现无琐化

大多数线程池实现都离不开锁的应用,如互斥量 pthread_mutex* 联合条件变量 pthread_cond*。家喻户晓,锁的应用对于程序性能影响较大,尽管现有的 pthread_mutex* 在锁的申请与开释方面做了较大的优化,然而,线程池的实现是能够做到无锁化的。

1. 常见线程池实现原理

如上图所示,工作队列由主线程和工作者线程共享,主线程将工作放进工作队列,工作者线程从工作队列中取出工作执行。共享工作队列的操作需在互斥量的爱护下平安进行,主线程将工作放进工作队列时若检测到以后待执行的工作数目小于工作者线程总数,则需应用条件变量唤醒可能处于期待状态的工作者线程。当然,还有其余中央可能也会应用到互斥量和条件变量,不再赘述。

2. 无锁化线程池实现原理

为解决无锁化的问题,须要防止共享资源的竞争,因而将共享工作队列加以拆分成每工作线程一个工作队列的形式。对于主线程放入工作和工作线程取出工作的竞争问题,能够采取环形队列的形式防止。在解决了锁机制之后,就只剩下条件变量的问题了,条件变量自身即解决条件满足时的线程通信问题,而信号作为一种通信形式,能够代替之,其大体编程范式为:

sigemptyset (&oldmask);sigemptyset (&signal_mask);sigaddset (&signal_mask, SIGUSR1);rc = pthread_sigmask(SIG_BLOCK, &signal_mask, NULL);if (rc != 0) {debug(TPOOL_ERROR, "SIG_BLOCK failed");    return -1;}...while (!condition) {rc = sigwait (&signal_mask, NULL);    if (rc != 0) {debug(TPOOL_ERROR, "sigwait failed");        return -1;    }}rc = pthread_sigmask(SIG_SETMASK, &oldmask, NULL);if (rc != 0) {debug(TPOOL_ERROR, "SIG_SETMASK failed");    return -1;}

须要 C /C++ Linux 服务器架构师学习材料加 qun(563998835)(材料包含 C /C++,Linux,golang 技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg 等),收费分享

3. 无锁化线程池具体实现

在无锁线程池中,区别于常见线程池的中央次要在于信号与条件变量、任务调度算法、减少或缩小线程数目后的工作迁徙,另外还有一点就是环形队列的实现参考了 Linux 内核中的 kfifo 实现。

(1) 信号与条件变量

信号与条件变量的区别次要在于条件变量的唤醒 (signal) 对于接管线程而言能够疏忽,而在未设置信号处理函数的状况下信号的接管会导致接管线程甚至整个程序的终止,因而须要在线程池产生线程之前指定信号处理函数,这样新生的线程会继承这个信号处理函数。多线程中信号的发送次要采纳 pthread_kill,为防止应用其余信号,本程序中应用了 SIGUSR1。

(2) 任务调度算法

常见线程池实现的任务调度次要在操作系统一级通过线程调度实现。思考到负载平衡,主线程放入工作时应采取适合的任务调度算法将工作放入对应的工作者线程队列,本程序目前已实现 Round-Robin 和 Least-Load 算法。Round-Robin 即轮询式地调配工作,Least-Load 即抉择以后具备起码工作的工作者线程放入。

(3) 工作迁徙

在线程的动静减少和缩小的过程中,同样基于负载平衡的考量,波及到现有工作的迁徙问题。负载平衡算法次要基于均匀工作量的思维,即统计以后时刻的总任务数目,均分至每一个线程,求出每个工作者线程应该减少或缩小的工作数目,而后从头至尾遍历,须要移出工作的线程与须要移入工作的线程执行工作迁徙,互相对消。最初若还有多进去的工作,再顺次调配。迁入工作不存在竞态,因为退出工作始终由主线程实现,而迁出工作则存在竞态,因为在迁出工作的同时工作者线程可能在同时执行工作。所以须要采纳原子操作加以修改,其次要思维即预取技术,大抵实现为:

do {work = NULL;    if (thread_queue_len(thread) <= 0)  //also atomic        break;    tmp = thread->out;    //prefetch work    work = &thread->work_queue[queue_offset(tmp)];} while (!__sync_bool_compare_and_swap(&thread->out, tmp, tmp + 1));if (work) {// do something 在线程的动静缩小后,原先线程上未能执行完的工作只须要由    // 主线程再次依据任务调度算法重新分配至其余存活的工作者线程队列中即可,不    // 存在上述问题,当然,此时能够同时执行负载平衡算法加以优化。}

(4) 环形队列

源码中环形队列实现次要参考了 linux 内核中 kfifo 的实现,如下图所示:

队列长度为 2 的整次幂,out 和 in 下标始终递增至越界后回转,其类型为 unsigned int,即 out 指针始终追赶 in 指针,out 和 in 映射至 FiFo 的对应下标处,其间的元素即为队列元素。

退出移动版