C++ 线程池

10 篇文章 9 订阅

总结一下C++里面的线程池,用的时候直接Copy就可以了。

为什么要用线程池呢?打个比方:不用线程池的话 每次有活,老板就要招一个人去干,干完活就把工人辞了。如果用线程池的话就是,老板招几个人干活,把任务按需分配给这几个人,活少的时候可能这几个人会闲着,但是活多的时候可以让这几个人排队一直干活。这种场景主要考虑如果活都比较小,后者可以省去频繁招聘员工带来的成本,忽略员工没活的时候成本消耗。就是说频繁的新建线程比较耗资源,如果同时新建线程比较多超过了一定的数量也会占用比较多的处理器资源,而线程空跑的时候资源消耗是比较小的,当然这个环节可以优化。

所谓线程池 ThreadPool 就是存放线程的池子,这个池子里面存放/管理着有限多个线程 thread,每个线程处理各自被分配的任务 task,这个任务就是需要被调用的函数 function 以及他的参数 argument

示例一(简单版)

我认为一个线程池应该满足具有以下几个特点:

  1. 有个容器queue存放task(函数指针以及参数),作为池子。
  2. 有个添加任务addTask的函数。
  3. 添加任务的时候,要检测当前活跃的线程数是否达到了指定个数,没达到的话就新建一个线程。
  4. 每一个线程都是循环体函数,不断的从queue中获取一个task运行。满足一定条件退出。

下面这个例子,使用Linux C 的线程函数来实现的线程池。
哦了,看一下代码吧。

ThreadPool.h

#ifndef THREAD_POOL_H
#define THREAD_POOL_H

#include <pthread.h>
#include <queue>

class ThreadPool {
  public:
    typedef void *(*function)(void *);  // 函数指针
    // 定义一个认为Task,包含一个函数指针和参数
    struct ThreadTask {
        function task;  // 函数指针
        void *arg;      // 参数
        ThreadTask(function task, void *arg) : task(task), arg(arg) {}
        ThreadTask() : task(nullptr), arg(nullptr){};
    };

	// 线程个数限制参数
    explicit ThreadPool(int threadsLimit = 100);
    ~ThreadPool();

	// 实际的工作线程,因为pthread_create()函数不能调用类成员函数,所以这里搞成友元函数
    friend void *workerThread(void *arg);

	// 往池子里面添加任务
    void addTask(function task, void *arg);

  private:
    pthread_mutex_t _mutex;
    pthread_cond_t _cond; // 信号弹
    pthread_attr_t _attr;

    std::queue<ThreadTask> _tasks;
    int _count;  // 当前工作线程个数
    int _idle;   // 当前空闲的线程个数
    int _threadsLimit;
    int _quit;  // 退出标识
    bool _valid; // 初始化成功标识
};

#endif  // THREAD_POOL_H

ThreadPool.cpp

#include "ThreadPool.h"
#include <errno.h>
#include <stdio.h>
#include <time.h>

ThreadPool::ThreadPool(int threadsLimit) : _valid(true), _count(0), _idle(0), _quit(0), _threadsLimit(threadsLimit)
{
    _valid = true;
	// 接下来初始化一系列变量,失败就false
    if (pthread_mutex_init(&_mutex, nullptr) != 0) {
        perror("pthread_mutex_init");
        _valid = false;
    }

    if (pthread_cond_init(&_cond, nullptr) != 0) {
        perror("pthread_cond_init");
        _valid = false;
    }

    if (pthread_attr_init(&_attr) != 0) {
        perror("pthread_attr_init");
        _valid = false;
    }

    if (pthread_attr_setdetachstate(&_attr, PTHREAD_CREATE_DETACHED) != 0) {
        perror("pthread_attr_setdetachstate");
        _valid = false;
    }
}

ThreadPool::~ThreadPool()
{
    if (_quit)
        return;

    pthread_mutex_lock(&_mutex);
    _quit = 1;

    if (_count > 0) {
        if (_idle > 0) {
            printf("idle[%d] count[%d]\n", _idle, _count);

            // 唤醒休眠的线程
            pthread_cond_broadcast(&_cond);
        }

        while (_count) {
            printf("count[%d] idle[%d]\n", _count, _idle);

            // 最后一个线程退出的时候会发送信号
            pthread_cond_wait(&_cond, &_mutex);
        }
    }

    pthread_mutex_unlock(&_mutex);

    if ((pthread_mutex_destroy(&_mutex)) != 0) {
        perror("pthread_mutex_destroy");
    }

    if ((pthread_cond_destroy(&_cond)) != 0) {
        perror("pthread_cond_destroy");
    }

    if ((pthread_attr_destroy(&_attr)) != 0) {
        perror("pthread_cond_destroy");
    }
}

// 工作线程
void *workerThread(void *arg)
{
    printf("[%lu] --> Start\n", pthread_self());

    struct timespec abstime;
    int timeout = 0;

    ThreadPool *pool = (ThreadPool *)arg;
    // 循环
    for (;;) {
        pthread_mutex_lock(&pool->_mutex);

        pool->_idle++;  // 新启动线程啥也没干,空闲线程+1

        //等待队列有任务到来 或者 收到线程池销毁通知
        while (pool->_tasks.empty() && !pool->_quit) {
            printf("[%lu] --> Waiting\n", pthread_self());

            clock_gettime(CLOCK_REALTIME, &abstime);
            abstime.tv_sec += 2;
			// 等他2秒钟,看看又没人叫我干活
            if (pthread_cond_timedwait(&pool->_cond, &pool->_mutex, &abstime) == ETIMEDOUT) {
                printf("[%lu] --> Wait timeout\n", pthread_self());
                timeout = 1;  // 没活
                break;
            }
        }

        pool->_idle--;  // 空闲线程-1,干点活或者结束本次循环
        // 有活了,干活干活
        if (!pool->_tasks.empty()) {
            printf("[%lu] --> Running a task\n", pthread_self());

            ThreadPool::ThreadTask task = pool->_tasks.front();
            pool->_tasks.pop();
            // 解锁让其他线程运行
            pthread_mutex_unlock(&pool->_mutex);

            // 取出一个任务,开始干活
            task.task(task.arg);

            // 干完了,新加锁进行下一步
            pthread_mutex_lock(&pool->_mutex);
        }

		// 程序退出了,进入析构函数的时候
        if (pool->_quit && pool->_tasks.empty()) {
            printf("[%lu] --> _quit && _tasks.empty()\n", pthread_self());
            pool->_count--; // 结束本线程

            //若线程池中没有线程,通知等待线程(主线程)全部任务已经完成
            if (pool->_count == 0) {
                pthread_cond_signal(&pool->_cond);
            }
            pthread_mutex_unlock(&pool->_mutex);
            break;
        }
		
		// 超时没等来活,先退了 (也可以不退的)
        if (timeout == 1) {
            pool->_count--;  //当前工作的线程数-1
            pthread_mutex_unlock(&pool->_mutex);
            break;
        }

        pthread_mutex_unlock(&pool->_mutex);
    }

    printf("[%lu] --> Exit\n", pthread_self());
    return nullptr;
}

void ThreadPool::addTask(function task, void *arg)
{
    if (!_valid || task == nullptr)
        return;

    pthread_mutex_lock(&_mutex);

    ThreadTask t(task, arg);
    _tasks.emplace(t);

    printf("addTask tasks[%d] count[%d] idle[%d]\n", (int)_tasks.size(), _count, _idle);

    if (_idle > 0) {
        // 有正在睡觉的线程的话,叫醒一个
        pthread_cond_signal(&_cond);
    } else if (_count < _threadsLimit) {
        pthread_t tid;
		// 启动一个线程,把当前实例指针作为参数传递过去,主要是要用到此类中的那些变量
        pthread_create(&tid, &_attr, workerThread, this); 
        _count++; // 线程数+1
    }

    pthread_mutex_unlock(&_mutex);
}

main.cpp

#include "ThreadPool.h"
#include <unistd.h>
#include <iostream>

void *task(void *arg)
{
    int *param = (int *)arg;
    // 打印一下线程id和参数
    printf("[%lu] --> Working on task %d\n", pthread_self(), *param);
    sleep(2);
    delete param;
    return nullptr;
}

int main()
{
    std::cout << "-------------" << std::endl;

    ThreadPool pool(4);  // 弄4个线程
    for (int i = 0; i < 20; i++) {  // 塞20个任务,以序号为参数
        std::cout << i << std::endl;
        int *arg = new int(i);
        pool.addTask(task, arg);
    }

    std::cout << "-------------" << std::endl;
}

运行结果

0
addTask tasks[1] count[0] idle[0]
1
addTask tasks[2] count[1] idle[0]
2
addTask tasks[3] count[2] idle[0]
[139729599485696] --> Start
3
addTask tasks[4] count[3] idle[0]
[139729591092992] --> Start
4
addTask tasks[5] count[4] idle[0]
5
addTask tasks[6] count[4] idle[0]
6
addTask tasks[7] count[4] idle[0]
7
addTask tasks[8] count[4] idle[0]
[139729582700288] --> Start
[139729582700288] --> Running a task
[139729582700288] --> Working on task 0
8
addTask tasks[8] count[4] idle[0]
9
addTask tasks[9] count[4] idle[0]
-------------
count[4] idle[0]
[139729574307584] --> Start
[139729574307584] --> Running a task
[139729574307584] --> Working on task 1
[139729599485696] --> Running a task
[139729599485696] --> Working on task 2
[139729591092992] --> Running a task
[139729591092992] --> Working on task 3
[139729582700288] --> Running a task
[139729582700288] --> Working on task 4
[139729591092992] --> Running a task
[139729591092992] --> Working on task 5
[139729574307584] --> Running a task
[139729574307584] --> Working on task 6
[139729599485696] --> Running a task
[139729599485696] --> Working on task 7
[139729582700288] --> Running a task
[139729582700288] --> Working on task 8
[139729574307584] --> Running a task
[139729574307584] --> Working on task 9
[139729599485696] --> _quit && _tasks.empty()
[139729599485696] --> Exit
[139729591092992] --> _quit && _tasks.empty()
[139729591092992] --> Exit
[139729582700288] --> _quit && _tasks.empty()
[139729582700288] --> Exit
[139729574307584] --> _quit && _tasks.empty()
[139729574307584] --> Exit

这个例子看不出来空闲线程的数量。可以看到最多4个线程来执行这20个任务。
可能有BUG,欢迎指出来。

示例二(多功能版)

上面那个是基础版,在一个开源项目里面看到一个线程池,感觉甚好,有空整理一下化为己用。

github

https://github.com/lmshao/ThreadPool

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值