本文链接:https://blog.csdn.net/xhjcehust/article/details/45844901

笔者之前照着通用写法练手写过一个小的线程池版本,最近几天复习了一下,发现大多数线程池实现都离不开锁的使用,如互斥量pthread_mutex*结合条件变量pthread_cond*。众所周知,锁的使用对于程序性能影响较大,虽然现有的pthread_mutex*在锁的申请与释放方面做了较大的优化,但仔细想想,线程池的实现是可以做到无锁化的,于是有了本文。

1.常见线程池实现原理

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

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

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

  1. sigemptyset (&oldmask);
  2. sigemptyset (&signal_mask);
  3. sigaddset (&signal_mask, SIGUSR1);
  4. rc = pthread_sigmask(SIG_BLOCK, &signal_mask, NULL);
  5. if (rc != 0) {
  6.     debug(TPOOL_ERROR, "SIG_BLOCK failed");
  7.     return -1;
  8. }
  9. ...
  10.  
  11.  
  12. while (!condition) {
  13. rc = sigwait (&signal_mask, NULL);
  14. if (rc != 0) {
  15. debug(TPOOL_ERROR, "sigwait failed");
  16. return -1;
  17. }
  18. }
  19.  
  20.  
  21. rc = pthread_sigmask(SIG_SETMASK, &oldmask, NULL);
  22. if (rc != 0) {
  23.     debug(TPOOL_ERROR, "SIG_SETMASK failed");
  24.     return -1;
  25. }

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

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

(1)   信号与条件变量

信号与条件变量的区别主要在于条件变量的唤醒(signal)对于接收线程而言可以忽略,而在未设置信号处理函数的情况下信号的接收会导致接收线程甚至整个程序的终止,因此需要在线程池产生线程之前指定信号处理函数,这样新生的线程会继承这个信号处理函数。多线程中信号的发送主要采用pthread_kill,为避免使用其他信号,本程序中使用了SIGUSR1。

(2)   任务调度算法

常见线程池实现的任务调度主要在操作系统一级通过线程调度实现。考虑到负载均衡,主线程放入任务时应采取合适的任务调度算法将任务放入对应的工作者线程队列,本程序目前已实现Round-Robin和Least-Load算法。Round-Robin即轮询式地分配工作,Least-Load即选择当前具有最少工作的工作者线程放入。

(3)   任务迁移

在线程的动态增加和减少的过程中,同样基于负载均衡的考量,涉及到现有任务的迁移问题。负载均衡算法主要基于平均工作量的思想,即统计当前时刻的总任务数目,均分至每一个线程,求出每个工作者线程应该增加或减少的工作数目,然后从头至尾遍历,需要移出工作的线程与需要移入工作的线程执行任务迁移,相互抵消。最后若还有多出来的工作,再依次分配。迁入工作不存在竞态,因为加入工作始终由主线程完成,而迁出工作则存在竞态,因为在迁出工作的同时工作者线程可能在同时执行任务。所以需要采用原子操作加以修正,其主要思想即预取技术,大致实现为:

  1. do {
  2. work = NULL;
  3. if (thread_queue_len(thread) <= 0) //also atomic
  4. break;
  5. tmp = thread->out;
  6. //prefetch work
  7. work = &thread->work_queue[queue_offset(tmp)];
  8. } while (!__sync_bool_compare_and_swap(&thread->out, tmp, tmp + 1));
  9. if (work)
  10. // do something

在线程的动态减少后,原先线程上未能执行完的任务只需要由主线程再次根据任务调度算法重新分配至其他存活的工作者线程队列中即可,不存在上述问题,当然,此时可以同时执行负载均衡算法加以优化。

(4)   环形队列

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

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

以上主要是一些方案性的说明,至于具体细节的实现有兴趣的读者可以参考https://github.com/xhjcehust/LFTPool,有问题欢迎随时联系讨论.

(转)高效线程池之无锁化实现(Linux C)的更多相关文章

  1. 高效线程池之无锁化实现(Linux C)

    from:http://blog.csdn.net/xhjcehust/article/details/45844901 笔者之前练手写过一个小的线程池版本(已上传至https://github.co ...

  2. java并发包&线程池原理分析&锁的深度化

          java并发包&线程池原理分析&锁的深度化 并发包 同步容器类 Vector与ArrayList区别 1.ArrayList是最常用的List实现类,内部是通过数组实现的, ...

  3. java无锁化编程一:目录

    假设我们用netty做服务,当接受到网络传输的码流,我们通过某种手段将这种传输数据解析成了熟悉的pojo,那这些pojo该如何进一步处理? 比如游戏中的抢购.场景业务等,对处理那种高并发的业务场景,如 ...

  4. Java CAS同步机制 原理详解(为什么并发环境下的COUNT自增操作不安全): Atomic原子类底层用的不是传统意义的锁机制,而是无锁化的CAS机制,通过CAS机制保证多线程修改一个数值的安全性。

    精彩理解:  https://www.jianshu.com/p/21be831e851e ;  https://blog.csdn.net/heyutao007/article/details/19 ...

  5. Netty源码学习系列之1-netty的串行无锁化

    前言 最近趁着跟老东家提离职之后.到新公司报道之前的这段空闲时期,着力研究了一番netty框架,对其有了一些浅薄的认识,后续的几篇文章会以netty为主,将近期所学记录一二,也争取能帮未对netty有 ...

  6. 高效线程池(threadpool)的实现

    高效线程池(threadpool)的实现 Nodejs编程是全异步的,这就意味着我们不必每次都阻塞等待该次操作的结果,而事件完成(就绪)时会主动回调通知我们.在网络编程中,一般都是基于Reactor线 ...

  7. linux无锁化编程--__sync_fetch_and_add系列原子操作函数

    linux支持的哪些操作是具有原子特性的?知道这些东西是理解和设计无锁化编程算法的基础. 下面的东西整理自网络.先感谢大家的分享! __sync_fetch_and_add系列的命令,发现这个系列命令 ...

  8. 【学习】005 线程池原理分析&锁的深度化

    线程池 什么是线程池 Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序 都可以使用线程池.在开发过程中,合理地使用线程池能够带来3个好处. 第一:降低资源消耗.通过重复 ...

  9. Nodejs事件引擎libuv源码剖析之:高效线程池(threadpool)的实现

    声明:本文为原创博文,转载请注明出处. Nodejs编程是全异步的,这就意味着我们不必每次都阻塞等待该次操作的结果,而事件完成(就绪)时会主动回调通知我们.在网络编程中,一般都是基于Reactor线程 ...

随机推荐

  1. vue cli 静态资源导入 路径

    1.public文件夹 使用绝对路径引入. 2.assets文件夹 使用相对路径引入. https://cli.vuejs.org/zh/guide/html-and-static-assets.ht ...

  2. CMU Database Systems - Indexes

    这章主要描述索引,即通过什么样的数据结构可以更加快速的查询到数据 介绍Hash Tables,B+tree,SkipList 以及索引的并行访问 Hash Tables hash tables可以实现 ...

  3. 【maven】在pom.xml中引入对json-lib的依赖dependency

    <dependency> <groupId>net.sf.json-lib</groupId> <artifactId>json-lib</art ...

  4. socket status 的状态变化详解

    原文链接: http://www.diranieh.com/SOCKETS/SocketStates.htm --------------------------------------------- ...

  5. Python3基础 keyword 查看所有的关键字

             Python : 3.7.3          OS : Ubuntu 18.04.2 LTS         IDE : pycharm-community-2019.1.3    ...

  6. linux普通用户添加root权限

    新增一个普通用户并进入该用户: [root@VM_0_7_centos ~]# groupadd mall [root@VM_0_7_centos ~]# useradd mall -m -d /ho ...

  7. Spring cloud微服务安全实战-6-3JWT改造之网关和服务改造

    网关上认证去做哪些改造 在网关上用jwt去解析用户信息,而不再发送校验令牌的请求了. 之前的时候网关上实际上写了很多的代码 包括认证,发check_token去把token请求,换成用户信息. 这俩是 ...

  8. synchronized 同步对象概念

    解决上述问题之前,先理解synchronized关键字的意义如下代码:   Object someObject =new Object(); synchronized (someObject){ // ...

  9. 【439】Tweets processing by Python

        参数说明: coordinates:Represents the geographic location of this Tweet as reported by the user or cl ...

  10. vfp

    DROP 从目前资料库中移出资料表格,并从磁碟上将它删除.DELETE 标示要删除的记录.只是标记,没有删除.ZAP 从资料表格中移除所有记录,仅保留资料表格结构.PACK 从目前资料库中移除标示为删 ...