Linux线程池的实现

        在分析了muduo的网络库源码之后,觉得muduo网络库关于线程池的实现很有意思,它利用boost库的function和bind这一对神器,摆脱了使用继承和虚函数的束缚,而且摆脱了线程回调函数的特异性,不用拘泥于回调函数的实现形式,我觉得这其中有很多手法值得我来借鉴。

源码下载地址:http://download.csdn.net/detail/yanchen0314/8192959

       关于线程池实现的基础:

      首先最基础的当然是互斥器的封装,我们使用Linux的互斥锁作为基础,采用RAII手法避免手动的去加锁和释放可能造成的遗漏,值得注意的是互斥器只用非递归的不可重用的互斥锁,非递归是因为防止重复加锁造成的死锁,不可重用是因为防止其发生拷贝而导致不可预期的事情发生,比如,你想操作一个临界区的共享资源,但是你无意中调用了拷贝构造函数对你的互斥器的副本加了锁而自己不知道,这样其实你的资源并没有被上锁,更糟糕的是如果你在解锁的时候不小心调用了互斥器的拷贝构造而对互斥器的副本解了锁,你自己此时以为已经解锁成功了,但其实真正的锁并没有被解开,等到下一次再对它进行加锁操作的时候,就立马导致死锁。让类不可重入的方法可以将类的拷贝构造函数定义成private。或者干脆让它继承自一个不可重入的基类,如boost::noncopyable。

class MutexLock:public boost::noncopyable
{
public:
    explicit MutexLock():holder_(0)
    {
       int ret = pthread_mutex_init(&mutex_,NULL);
       assert(ret==0);
    }
    ~MutexLock(void)
    {
        assert(holder_==0);
        pthread_mutex_destroy(&mutex_);
        assert(holder_==0);
    }
    void lock(void)
    {
        pthread_mutex_lock(&mutex_);
        holder_ = CurrentThread::gettid();
    }
    void unlock(void)
    {
        pthread_mutex_unlock(&mutex_);
        holder_ = 0;
    }
    pthread_mutex_t *getPthreadMutex(void)
    {
       return &mutex_;
    }
    bool isLockByThisThread(void)
    {
        return holder_ == CurrentThread::gettid();
    }
    void assertLocked(void)
    {
        assert(isLockByThisThread());
    }
private:
    pthread_mutex_t mutex_;
    pid_t holder_;
};

class MutexLockGuard:public boost::noncopyable
{
public:
    explicit MutexLockGuard(MutexLock &lock)
        :mutex_(lock)
    {
        mutex_.lock();   //使用RAII手法在构造的时候加锁
    }
    ~MutexLockGuard()
    {
        mutex_.unlock();//使用RAII手法在析构的时候解锁,防止遗漏
    }
private:
    MutexLock &mutex_;
};

对一个共享数据加锁可以用:

Mutex mutex_;

{

   MutexLockGuardlock(mutex_);//注意千万不要将锁嵌套的调用

   Count++;

}


        条件变量的封装,线程池的正常工作需要用到条件变量监控任务的到来和进行任务的同步。当线程池有任务到来时它来唤醒线程去执行任务,当没有任务时他等待处于阻塞状态。
class Condition:public boost::noncopyable
{
public:
    explicit Condition(MutexLock &lock)
        :mutex_(lock)
    {
       pthread_cond_init(&pcon_,NULL);
    }
    ~Condition()
    {
        pthread_cond_destroy(&pcon_);
    }
    void wait(void)
    {
        pthread_cond_wait(&pcon_,mutex_.getPthreadMutex());
    }
    void notify(void)
    {
        pthread_cond_signal(&pcon_);
    }
    void notifyAll(void)
    {
        pthread_cond_broadcast(&pcon_);
    }
    void waitForSeconds(int seconds)
    {
        struct timespec abstime;
        clock_gettime(CLOCK_REALTIME,&abstime);
        abstime.tv_sec += seconds;
        pthread_cond_timedwait(&pcon_,mutex_.getPthreadMutex(),&abstime);
    }
private:
    MutexLock &mutex_;
    pthread_cond_t pcon_;
};

在对Condition执行wait操作时互斥器必须先加锁

        

        线程的封装,在标准的Linux编程中,线程函数的形式可能必须满足某种特定的格式,如:void *(threadFunction)(void*argv)。但是这样太呆板了,我们能不能使它变得更加多变,变得不拘泥于特定的形式,这似乎是个棘手的问题,直到我看到了boost库的bind和function这对神器,有了他们我们就可以打破格式的限制,不管是非成员函数还是类的成员函数都可以隐式转换成特定的形式。

class CThread:public boost::noncopyable
{
public:
    typedef function<void ()> ThreadFunc;
    explicit CThread(const ThreadFunc &func,const std::string&name="");
    ~CThread();
    const std::string &name()const{return name_;}
    pid_t tid()const{return tid_;}
    bool started()const{return started_;}
    void start(void);
    int join(void);
private:
    static void *startThread(void *obj);
    void runInThread(void);
    pthread_t pthreadID_;
    pid_t tid_;
    std::string name_;
    bool started_;
    ThreadFunc func_;
};
CThread在构造函数时指定线程的回调函数

typedef function<void ()> ThreadFunc;
explicit CThread(const ThreadFunc &func,const std::string&name="")
    :name_(name)
    ,func_(func)
    ,tid_(0)
    ,pthreadID_(0)
    ,started_(false)
{

}

再让我们看看start()函数做了什么事:

void CThread::start()
{
    assert(!started_);
    int err=pthread_create(&pthreadID_,NULL,&startThread,this);
    started_ = true;
    if(err<0)
    {
        perror("Thread::start() pthread_create error");
    }
}

这创建的线程回调函数是startThread,是CThread的一个私有的成员函数,好像并没有执行构造时指定的回调函数func啊,别急往下看startThread函数做了啥;

void *CThread::startThread(void *obj)
{
    CThread *pThread = static_cast<CThread *>(obj);
    pThread->runInThread();
    return NULL;
}
原来他调用了另一个成员函数runInThread,可是还是没看到startThread的影子啊,接着向下看;

void CThread::runInThread()
{
    tid_ = CurrentThread::gettid();
    func_();
}
哦,原来真正的调用在这里,这样巧妙的设计就能够达到消除特异性的差异,让我们迫不及待的试一下吧。

void function1(void)
{
    printf(“function1\n”);
}

void function(int argc)
{
    printf(“function2:%d\n”,argc);
}

Class Test
{
   ….
   public:
       void function3(const char *argv)
{
     printf(“function3:%s\n”,argv);
}
}
CThread thread1(boost::bind(&function1));
thread1.start();
CThread thread2(boost::bind(&function2,5));
thread2.start();
Test t1;
const char *pData = “Hello”;
CThread thread3(boost::bind(&Test::function3,t1,pData));
thread3.start();

由此可见不管是成员函数还是非成员函数,不管带几个参数,这样的限制都被屏蔽了。

        线程池的封装:

class ThreadPool
{
public:
    typedef boost::function<void ()> Task;
    explicit ThreadPool(std::string name="");
    ~ThreadPool(void);
    void start(int numberThreads);
    void stop(void);
    void run(const Task &f);
private:
    void runInThread(void);
    Task take(void);
    MutexLock mutex_;
    Condition cond_;
    std::string name_;
    boost::ptr_vector<CThread> threads_;
    std::deque<Task> queue_;
    bool runing_;
};
void ThreadPool::start(int numberThreads)
{
    assert(threads_.empty());
    runing_ = true;
    threads_.reserve(numberThreads);
    for(int i=0;i<numberThreads;++i)
    {
        char id[32] ={0};
        snprintf(id,31,"%d",i);
        threads_.push_back(new CThread(boost::bind(&ThreadPool::runInThread,this),name_+id));
        threads_[i].start();
    }
}
在这里指定你创建的线程池中有几个线程,每个线程的回调函数都去执行runInThread;那么runInThread到底在干什么呢?

void ThreadPool::runInThread()
{//这里才是线程真正执行的函数
    while(runing_)
    {//线程不断的在循环的等待任务,这样线程在不调用stop的情况下就不会退出
        Task task(take());//每个线程在这里等待,等待到后就执行
        if(task)
        {
            task();
        }
    }
}

它似乎是去获取一个任务后然后去执行,那我们来看看任务是怎么获取的。

ThreadPool::Task ThreadPool::take()
{
    MutexLockGuard lock(mutex_);
    if(queue_.empty() && runing_)
    {
       cond_.wait();//在没有任务的时候,就在这里阻塞
    }
    Task task;
    if(!queue_.empty())
    {
       task = queue_.front();
       queue_.pop_front();
    }
    return task;
}
看明白了吧,这里有一个条件变量来负责等待任务,如果人祸列表为空就等待,一直等待有任务到来为止,那任务什么时候到来,到来的又是什么样的任务呢,别急,我们往下面看。

void ThreadPool::run(const ThreadPool::Task &f)
{//往任务列表中去添加任务,唤醒阻塞的线程去执行
    if(threads_.empty())
    {
        f();
    }
    else
    {
        MutexLockGuard lock(mutex_);
        queue_.push_back(f);
        cond_.notify();
    }
到这里疑惑都解开了吧,当用户执行run的时候,指定了线程回调函数f,如果线程池里没有一个线程,那么f()就直接在主线程里执行了,如果有其他的线程在等待,那么将任务加入到任务执行列表,然后通知所有正在等待的线程,有任务来了,线程们一收到通知立刻唤醒去抢占任务。在这里就都明白了吧。


 为什么要这样封装线程池?

1.其中用到RAII手段,可以防止你手动加锁导致的一系列的麻烦。

2.可以消除线程回调函数的特异性,接口不会受到形式的拘泥

3.使用Linux自带的条件变量进行抢占式执行,能够使程序有足够的效率去响应任务

4.随时随地控制线程的数量,能够方便将你的程序更加充分的利用现有的硬件资源

我能想到的好处就这些了,至于其他的妙用希望读者自己去发掘。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个简单的基于Linux线程池的快速排序实现: ```c #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define THREAD_MAX 8 #define MAX 1000 int array[MAX]; int count = 0; pthread_mutex_t mutex; typedef struct { int left; int right; } task_t; typedef struct { task_t *tasks; int count; int head; int tail; pthread_mutex_t mutex; pthread_cond_t cond; } task_queue_t; task_queue_t task_queue; void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } int partition(int *array, int left, int right) { int pivot = array[right]; int i = left - 1; for (int j = left; j < right; j++) { if (array[j] < pivot) { i++; swap(&array[i], &array[j]); } } swap(&array[i + 1], &array[right]); return i + 1; } void quick_sort(int *array, int left, int right) { if (left < right) { int pivot_index = partition(array, left, right); quick_sort(array, left, pivot_index - 1); quick_sort(array, pivot_index + 1, right); } } void *worker_thread(void *arg) { task_t task; while (1) { pthread_mutex_lock(&task_queue.mutex); while (task_queue.count == 0) { pthread_cond_wait(&task_queue.cond, &task_queue.mutex); } task = task_queue.tasks[task_queue.head]; task_queue.head = (task_queue.head + 1) % MAX; task_queue.count--; pthread_mutex_unlock(&task_queue.mutex); quick_sort(array, task.left, task.right); pthread_mutex_lock(&mutex); count++; pthread_mutex_unlock(&mutex); } return NULL; } void submit_task(task_t task) { pthread_mutex_lock(&task_queue.mutex); while (task_queue.count == MAX) { pthread_cond_wait(&task_queue.cond, &task_queue.mutex); } task_queue.tasks[task_queue.tail] = task; task_queue.tail = (task_queue.tail + 1) % MAX; task_queue.count++; pthread_cond_signal(&task_queue.cond); pthread_mutex_unlock(&task_queue.mutex); } int main() { pthread_t threads[THREAD_MAX]; task_t task; pthread_mutex_init(&mutex, NULL); task_queue.tasks = malloc(sizeof(task_t) * MAX); task_queue.count = 0; task_queue.head = 0; task_queue.tail = 0; pthread_mutex_init(&task_queue.mutex, NULL); pthread_cond_init(&task_queue.cond, NULL); for (int i = 0; i < THREAD_MAX; i++) { pthread_create(&threads[i], NULL, worker_thread, NULL); } for (int i = 0; i < MAX; i += 100) { task.left = i; task.right = i + 99; submit_task(task); } while (count < MAX / 100) { // wait for all tasks to complete } for (int i = 0; i < MAX; i++) { printf("%d ", array[i]); } return 0; } ``` 该代码首先定义了一个 `task_t` 结构体,用于存储每个快速排序任务的左右边界。然后定义了一个任务队列 `task_queue_t`,用于存储所有待处理的任务。该队列包含一个 `tasks` 数组,用于存储所有任务,`count` 属性表示队列中当前的任务数,`head` 和 `tail` 属性表示队列头和尾的位置,`mutex` 和 `cond` 属性分别用于线程同步。定义了一个 `submit_task` 函数,用于将任务提交到任务队列中。 然后创建多个工作线程,每个工作线程不断从任务队列中获取任务,执行快速排序。每当一个任务完成时,使用互斥锁 `mutex` 统计完成的任务数。主线程循环等待所有任务完成,最后输出排序后的结果。 该代码中使用了互斥锁和条件变量来实现线程同步,保证任务队列的并发访问安全。同时使用了 Linux 系统的线程池来管理工作线程,避免了手动创建和销毁线程的复杂性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值