主要内容

  1. ScopedLock
  2. 队列实现
  3. 线程池实现

在正式讲解线程池实现之前,先讲解两个有用的工具类:

  • ScopedLock
  • fifo队列

ScopedLock:

ScopedLock是局域锁的实现(我也不知道叫什么,姑且这么说吧),它使用了C++中RAII(Resource acquisition is initialization资源获取即初始化),这种技巧实现的锁可在代码块开始处初始化锁,在代码块结束处释放锁,可省去try catch这样的语句,具体实现如下:

struct ScopedLock {
private:
pthread_mutex_t *m_;
public:
ScopedLock(pthread_mutex_t *m): m_(m) {
VERIFY(pthread_mutex_lock(m_)==);
}
~ScopedLock() {
VERIFY(pthread_mutex_unlock(m_)==);
}
};

其中宏VERIFY定义如下,这时贯穿于整个项目并最常用的一个宏:

#ifdef NDEBUG
#define VERIFY(expr) do { if (!(expr)) abort(); } while (0)
#else
#define VERIFY(expr) assert(expr)
#endif

ScopedLock的使用方法如下:

void func() {
......
{
ScopedLock lock(&m_);
......
}
......
}

在代码块{}定义一个lock变量即可,在}处便能自动调用析构函数,从而自动释放锁,这种技巧在《c++必知必会》中也是强烈推荐的一种技巧。

fifo队列实现

代码中的fifo队列实现了简单生产者消费者模型,提供了阻塞非阻塞选项,实现代码在fifo.h文件下,我们首先看看类定义:

template<class T>
class fifo {
public:
fifo(int m=); //默认limit为了,即代表队列大小无限制
~fifo();
//默认实现为阻塞队列
bool enq(T, bool blocking=true); //入队,默认为阻塞
void deq(T *); //出队
bool size(); //队列大小 private:
std::list<T> q_; //队列
pthread_mutex_t m_; //互斥量保护队列
pthread_cond_t non_empty_c_; // q went non-empty
pthread_cond_t has_space_c_; // q is not longer overfull
unsigned int max_; //maximum capacity of the queue, block enq threads if exceeds this limit
};

该实现仅是对C++ STL的list进行了简单的封装,在这基础上增加了些条件变量控制,我们主要看enq和deq的实现。

template<class T> bool
fifo<T>::enq(T e, bool blocking)
{
//使用了局域锁,在函数返回之前便会释放锁
ScopedLock ml(&m_);
while () {
//当limit = 0(即代表大小无限制时),插入元素
//或当队列大小小于max_时,插入元素
if (!max_ || q_.size() < max_) {
q_.push_back(e);
break;
}
if (blocking) //若为阻塞队列且队列中没有空间容纳新元素,则等待消费者取走元素
VERIFY(pthread_cond_wait(&has_space_c_, &m_) == );
else //若不是阻塞队列,当队列中没有空间可容纳新元素时,立即返回false
return false;
}
//现在队列中至少有一个元素,通知其它等待在该条件变量上的线程(生产者)
VERIFY(pthread_cond_signal(&non_empty_c_) == );
return true;
} template<class T> void
fifo<T>::deq(T *e)
{
ScopedLock ml(&m_); while() {
//当队列为空时,等待
if(q_.empty()){
VERIFY (pthread_cond_wait(&non_empty_c_, &m_) == );
} else { //否则,取出头部元素,注意函数中传入的参数是个指针
*e = q_.front();
q_.pop_front();
if (max_ && q_.size() < max_) { //通知其它线程队列中现在至少有一个空位
VERIFY(pthread_cond_signal(&has_space_c_)==);
}
break;
}
}
return;
}

这样一个简单的fifo队列便实现了,它也是后面线程池实现的一个重要环节。

线程池实现

线程池实现中技巧性要求有点高,其中涉及函数指针、类指针、函数对象以及回调等技巧。首先我们来看它的定义,见thr_pool.h文件:

class ThrPool {

    public:
struct job_t {
void *(*f)(void *); //function point
void *a; //function arguments
}; ThrPool(int sz, bool blocking=true); //默认使用阻塞队列
~ThrPool();
//添加工作,其中第一个参数是一个类指针,第二个参数是一个类函数,其参数是一个类型A,
//第三个参数是第二个类函数指针的参数类型变量
template<class C, class A> bool addObjJob(C *o, void (C::*m)(A), A a);
void waitDone();
//获得Job
bool takeJob(job_t *j); private:
pthread_attr_t attr_;
int nthreads_; //线程数目
bool blockadd_; fifo<job_t> jobq_; //job队列
std::vector<pthread_t> th_; //线程数组
//私有类,供addObjJob内部调用
bool addJob(void *(*f)(void *), void *a);
}; template <class C, class A> bool
ThrPool::addObjJob(C *o, void (C::*m)(A), A a)
{
//内部类,隐藏了实现细节
class objfunc_wrapper {
public:
C *o; //类指针,也即第一个变量
void (C::*m)(A a); //类函数
A a;
static void *func(void *vvv) {
//将vvv转换为objfunc_wrapper类
objfunc_wrapper *x = (objfunc_wrapper*)vvv;
//将转换后的各变量赋值给本类中的各变量
C *o = x->o;
void (C::*m)(A ) = x->m;
A a = x->a;
(o->*m)(a); //执行函数,回调的执行
delete x; //
return ;
}
}; objfunc_wrapper *x = new objfunc_wrapper;
x->o = o;
x->m = m;
x->a = a;
//添加工作回调函数
return addJob(&objfunc_wrapper::func, (void *)x);
}

看上面的实现特别是addObjJob,确认令人惊叹(大神忽略),这样工作类即可很容易的添加进去,使用线程池时也会更加方便,仅需实现相应的工作类及工作类的回调函数即可。接下来我们看thr_pool.cc文件中的具体实现:

//线程执行方法,while循环中获取队列中的工作,因为队列默认是阻塞队列
//线程在没获取到工作时,将阻塞在相应的条件变量上
static void *
do_worker(void *arg)
{
ThrPool *tp = (ThrPool *)arg; //将this转换为ThrPool指针
while () {
ThrPool::job_t j;
if (!tp->takeJob(&j))
break; //die (void)(j.f)(j.a); //执行工作
}
pthread_exit(NULL);
} //if blocking, then addJob() blocks when queue is full
//otherwise, addJob() simply returns false when queue is full
ThrPool::ThrPool(int sz, bool blocking)
: nthreads_(sz),blockadd_(blocking),jobq_(*sz)
{
pthread_attr_init(&attr_);
pthread_attr_setstacksize(&attr_, <<); for (int i = ; i < sz; i++) {
pthread_t t;
//注意这里函数是do_worker,添加的参数为this,这样在do_worker函数中方便取出更多的信息
VERIFY(pthread_create(&t, &attr_, do_worker, (void *)this) ==);
th_.push_back(t);
}
} //IMPORTANT: this function can be called only when no external thread
//will ever use this thread pool again or is currently blocking on it
ThrPool::~ThrPool()
{
for (int i = ; i < nthreads_; i++) {
job_t j;
j.f = (void *(*)(void *))NULL; //poison pill to tell worker threads to exit
jobq_.enq(j);
} for (int i = ; i < nthreads_; i++) {
VERIFY(pthread_join(th_[i], NULL)==);
} VERIFY(pthread_attr_destroy(&attr_)==);
} //添加工作的私有类,初始化job_t类,并添加到队列中
bool
ThrPool::addJob(void *(*f)(void *), void *a)
{
job_t j;
j.f = f;
j.a = a; return jobq_.enq(j,blockadd_);
} //获取队列中的工作回调,注意传入的是指针
bool
ThrPool::takeJob(job_t *j)
{
jobq_.deq(j);
return (j->f!=NULL);
}

该线程池的实现确认令人咋舌,很巧妙的将回调类转换成了内部的job_t类,也不失为一个很好的c++学习案例。

使用该线程池很简单,只需定义好相应的事件回调类,然后初始化线程池,再将回调类添加(addObjJob)到线程池中即可

MIT 2012分布式课程基础源码解析-线程池实现的更多相关文章

  1. MIT 2012分布式课程基础源码解析一-源码概述

    课程主页 课程介绍:本课程会在给出的源码的基础上要求完成8个lab Lab overviewLab 1 - Lock ServerLab 2 - Basic File ServerLab 3 - MK ...

  2. MIT 2012 分布式课程基础源码解析-底层通讯实现

    本节内容和前节事件管理封装是息息相关的,本节内容主要包含的代码在connection{.h, .cc}中. 这里面最主要的有两个类:connection类和tcpsconn类,connetion类主要 ...

  3. MIT 2012分布式课程基础源码解析-事件管理封装

    这部分的内容主要包括Epoll/select的封装,在封装好相应函数后,再使用一个类来管理相应事件,实现的文件为pollmgr.{h, cc}. 事件函数封装 可看到pollmgr.h文件下定一个了一 ...

  4. nginx源码分析线程池详解

    nginx源码分析线程池详解 一.前言     nginx是采用多进程模型,master和worker之间主要通过pipe管道的方式进行通信,多进程的优势就在于各个进程互不影响.但是经常会有人问道,n ...

  5. Elasticsearch源码分析—线程池(十一) ——就是从队列里处理请求

    Elasticsearch源码分析—线程池(十一) 转自:https://www.felayman.com/articles/2017/11/10/1510291570687.html 线程池 每个节 ...

  6. JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor

    JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor.它主要用来在 ...

  7. JUC源码分析-线程池篇(二)FutureTask

    JUC源码分析-线程池篇(二)FutureTask JDK5 之后提供了 Callable 和 Future 接口,通过它们就可以在任务执行完毕之后得到任务的执行结果.本文从源代码角度分析下具体的实现 ...

  8. JUC源码分析-线程池篇(三)Timer

    JUC源码分析-线程池篇(三)Timer Timer 是 java.util 包提供的一个定时任务调度器,在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次. 1. Ti ...

  9. JUC源码分析-线程池篇(一):ThreadPoolExecutor

    JUC源码分析-线程池篇(一):ThreadPoolExecutor Java 中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池.在开发过程中,合理地使用线程池 ...

随机推荐

  1. android---APN切换

    android手机客户端在上传文件时,有时候会一直失败,其可能的原因是APN的设置.wap下的成功率极低,所以在进行文件上传时最好设置下 apn为net形式.下面是我在网上找的一些代码,是由wap转n ...

  2. 文件I/O(不带缓冲)之lseek函数

    每个打开的文件都有一个与其相关联的“当前文件偏移量”(current file offset).它通常是一个非负整数,用以度量从文件开始处计算的字节数.通常,读.写操作都从当前文件偏移量处开始,并使偏 ...

  3. Apache rewrite 详解

    用rewrite可实现的部分:URL根目录搬迁,多目录查找资源,阻止盗连你的图片,拒绝某些主机访问,基于时间重写,据浏览器类型重写,动态镜像远程资源,外部重写程序模板,等等 详见下表: 目标 重写设置 ...

  4. WinServer 之 发布WebService后调用出现" The test form is only available for requests from the local machine. "

    当您尝试从远程计算机访问 Web 服务时,不会显示“调用”按钮.并且,您会收到以下错误信息: The test form is only available for requests from the ...

  5. 数据结果与算法分析(1)——算法分析

          在确定一个算法正确的同时,也要保证算法的有效性.算法分析的最重要的标准时运行时间T(N),运行时间与输入元素个数N有关. 数学基础         T(N) = O(f(N)) 表示T(N ...

  6. 使用jQuery获取Bootstrap Switch的值

    $('#switcher').bootstrapSwitch('state'); // true || false $('#switcher').bootstrapSwitch('toggleStat ...

  7. files_dir

    一.opendir() —— 打开目录 opendir( 打开的当前目录 );   二.closedir() —— 关闭目录   三.readdir() —— 返回目录中的各个元素,返回上一个并且指向 ...

  8. JSON数据理解

    话说JSON数据平常用的确实挺多的,但是基本上只知道怎么用,对其一些细节并没有整理过,今儿趁着下午有点空,坐下来,学习整理下,并分享出来. 对于JSON,首先它只是一种数据格式,并非一种语言,虽然和j ...

  9. 【转】MyBatis学习总结(四)——解决字段名与实体类属性名不相同的冲突

    [转]MyBatis学习总结(四)——解决字段名与实体类属性名不相同的冲突 在平时的开发中,我们表中的字段名和表对应实体类的属性名称不一定都是完全相同的,下面来演示一下这种情况下的如何解决字段名与实体 ...

  10. Unity3D之Ugui 制作弹框

    创建一个UI控件. 这里通过按钮的点击取控制弹框的显示或者隐藏.给按钮Button绑定一个脚本. 将Panel初始化设置为隐藏.就可以实现了. using UnityEngine; using Sys ...