スレッドプールのロック効率的な実現の(RPM)(LinuxのC)

 

このリンク: 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,有问题欢迎随时联系讨论.

おすすめ

転載: www.cnblogs.com/schips/p/11516606.html