c++实现线程池

c++线程池的实现

github仓库地址
线程池主要由两部组成,一是任务队列,二是线程池,任务队列存储要处理的任务,线程池分配线程去处理要处理的任务,所以我们完成整个过程分两个部分,一是维护一个线程安全的任务队列,二是创建一个线程池。

一、维护一个线程安全的任务队列

知识要点

  1. 互斥锁
  2. 条件变量
  3. 模板
  4. 万能引用加完美转发
  5. 虚假唤醒

源码如下:

#ifndef TASKQUE_HPP
#define TASKQUE_HPP
#include<iostream>
#include<mutex>
#include<queue>
#include<condition_variable>
#include<assert.h>
template<typename T>
class TaskQue
{
public:
TaskQue():ifstop(false){}

template<typename U>//相当于模板函数独立出来了
void push(U&&item)//万能引用
{
    {
        // static_assert(std::is_same<T,U>::==true);//判断模板类型是否一样
        std::lock_guard<std::mutex>locker(m_mutex);
        m_queue.push(std::forward<U>(item));//完美转发 保证之传进来的参数类型的属性(左值右值等等)
    }
    m_cond.notify_all();
}
bool pop(T&item)
{
    std::unique_lock<std::mutex>locker(m_mutex);
    while(m_queue.empty()||ifstop)
    {
        m_cond.wait(locker);
    }
    if(m_queue.empty())
    return false;
    item=(std::move(m_queue.front()));
    m_queue.pop();
    return true;
}


size_t size()const
{
    std::lock_guard<std::mutex>locker(m_mutex);
    return m_queue.size();
}
bool empty()
{
    std::lock_guard<std::mutex>locker(m_mutex);
    return m_queue.empty();
}
void stop()
{
    {
        std::lock_guard<std::mutex>locker(m_mutex);
        ifstop=true;
    }
    m_cond.notify_all();
}
private:
std::mutex m_mutex;
std::queue<T> m_queue;
std::condition_variable m_cond;
bool ifstop;//c++11特性
};
#endif

说明:
1.其中类中push方法运用函数模板实现万能引用,使之传入进来的对象可以是任意类型的并且是任意属性的(左值或者右值),利用完美转发可以实现传进来的参数保留其参数原来的属性。
2.在pop方法中当队列为空或者停止条件为true的时候使用条件变量使线程阻塞,减少线程资源的消耗
3.由于任务队列是线程中的公共资源,所以每个方法中使用互斥锁使共享资源保持完整性。

二、创建一个线程池

知识要点:

  1. 函数重载
  2. 智能指针
  3. function多态函数包装器的使用
  4. future期物的使用(异步提供)
  5. 函数后置返回类型(auto和decltype的混合使用)
  6. 多参函数模板的使用

源码如下:


#ifndef THREADPOOL_HPP
#define THREADPOOL_HPP

#include<memory>
#include<iostream>
#include<queue>
#include<thread>
#include<vector>
#include<mutex>
#include<condition_variable>
#include<functional>
#include<future>
#include <utility>
#include"TaskQue.hpp"

using namespace std;
class ThreadPool
{
public:
    explicit ThreadPool(const int threads=4):work_threads(vector<thread>(threads)),m_shutdown(false){
        std::cout<<"pool begin"<<std::endl;
        // std::thread();
    }
    void init()//初始化线程池
    {
        for(int i=0;i<work_threads.size();++i)
        {
            work_threads[i]=thread(Work(this,i));//分配工作线程
        }
    }
    void shutdown()//关闭线程池
    {
        m_shutdown=true;
        cond.notify_all();
        for(int i=0;i<work_threads.size();++i)
        {
            if(work_threads[i].joinable())
            {
                work_threads[i].join();
            }
        }
        std::cout<<"shut down"<<std::endl;
    }
    //禁止拷贝构造和移动语义
    ThreadPool(const ThreadPool &) = delete;
    ThreadPool(ThreadPool &&) = delete;
    ThreadPool &operator=(const ThreadPool &) = delete;
    ThreadPool &operator=(ThreadPool &&) = delete;

    template<typename F,typename... Args>//多参数的函数模板
    auto submit(F&&f,Args&& ...args)->std::future<decltype(f(args...))>//多参数的函数模板化
    {
        std::function<decltype(f(args ...))()>func=std::bind(std::forward<F>(f),std::forward<Args>(args) ...);//避免左右值的歧义
                                                                                                            //用bind进行包装绑定 调用的时候就不需要调用参数
        auto taskptr=make_shared<packaged_task<decltype(f(args ...))()>>(func);//创建了一个智能指针 std::packaged_task的绑定构造 用于期物的创建
        //  auto taskptr=make_shared<packaged_task<decltype(f(args ...)())>>(std::bind(std::forward<F>(f),std::forward<Args>(args) ...));
        function<void()> task_func=[taskptr](){
            (*taskptr)();
        };//任意类型的返回值
        tasks.push(task_func);//压入任务队列
        cond.notify_one();//唤醒其中的一个线程
        return taskptr->get_future();//返回一个期物 用于在不同线程调用函数返回值
    }
    ~ThreadPool()
    {
    shutdown();//线程池析构的时候进行关闭
    }
private:
    class Work{
        public:
            Work(ThreadPool*pool,int id):m_pool(pool),m_id(id){//初始化工作线程
                cout<<"work id is "<<m_id<<endl;
            }
        void operator()()//重载()开始工作
        {
                //基础任务函数
                std::function<void()>task;
                //是否正在进行出队
                bool dequeued;
            while(!m_pool->m_shutdown)
            {    
                {
                    std::unique_lock<std::mutex>locker(m_pool->m_mutex);
                    while(m_pool->tasks.empty())
                    {
                        std::cout<<m_id<<" is blocked"<<std::endl;//阻塞
                        m_pool->cond.wait(locker);
                    }
                    std::cout<<"be waked id is "<<m_id<<std::endl;
                    dequeued=m_pool->tasks.pop(task);
                }
                if(dequeued)
                {
                    task();//执行任务
                }
            }
        }

        private:
            int m_id;       //工作线程的id
            ThreadPool *m_pool;//所属的线程池
    };
    TaskQue<std::function<void()>>tasks;//std::function 用于包装函数
    vector<thread>work_threads;
    condition_variable cond;
    mutex m_mutex;
    bool m_shutdown;
};

#endif

说明:
1、初始化线程池,使用内部类work重载“()”使之变为函数对象实现线程的工作,若是任务队列为空线程就使用条件变量阻塞。
2、submit函数结合auto和decltype使用函数后置返回类型,返回类型是一个期物,为了在其他线程中得到该线程的状态(状态的共享)。
3、submit任务提交函数,使用了多餐函数模板,使函数对象的参数可以任意任意参数,并且可以接受lambda表达式。
4、submit函数中先使用function包装了传进来的函数,使用智能指针在包装一层(为了在另一个线程中返回期物,防止线程间资源的泄漏)。
5、使用function<void()>类型为无返回类型的无参数的函数对象的function对象对任务函数进行最后的包装,利用lambda进行传递真正的任务函数,实现多态擦除函数类型,将任务函数以function<void()>对象的形式传入任务队列,实现了对任务函数的多态化的兼容。
6、shutdown函数实现对线程池的关闭,利用条件变量唤醒所有的线程立刻完成当前的工作,调用join实现线程的回收。线程调用析构函数时候会先判断线程是否处于可连结状态(joinable),若处于可连接状态,线程将不会被资源回收,调用join可以实现线程脱离可连接状态。
7、要避免虚假唤醒的问题,因为操作系统的不确定性,他可能会在无意中执行唤醒原语将线程唤醒,而此时其实任务队列中的并没有任务,这种情况就叫虚假唤醒,解决方法是:将 if(m_pool->tasks.empty())中的 if 改为 while 因为这样处理能再一次检查队列的情况以判断是虚假唤醒还是真是唤醒,确保后续的逻辑不会出错。

以上就是对线程池的理解及代码,代码不多重在对c++11语法糖的理解。
另外附上github源代码地址:
github仓库地址

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 实现线程池的方法有很多,但是最常见的方法是使用队列来维护任务。每个线程都在队列中等待任务,当有新任务到达时,就从队列中取出一个任务并执行。这样,每个线程都可以在并行执行任务,而不需要创建新的线程。 在C语言中实现线程池的代码如下: ``` #include <pthread.h> #include <stdio.h> #include <stdlib.h> #define NUM_THREADS 5 void *print_hello(void *threadid) { long tid; tid = (long)threadid; printf("Hello World! It's me, thread #%ld!\n", tid); pthread_exit(NULL); } int main(int argc, char *argv[]) { pthread_t threads[NUM_THREADS]; int rc; long t; for (t = 0; t < NUM_THREADS; t++) { printf("In main: creating thread %ld\n", t); rc = pthread_create(&threads[t], NULL, print_hello, (void *)t); if (rc) { printf("ERROR; return code from pthread_create() is %d\n", rc); exit(-1); } } pthread_exit(NULL); } ``` ### 回答2: 要实现线程池,首先需要先了解线程池的基本概念和原理。 线程池是一种用来管理和复用线程的技术,它能够维护一个线程队列,按需创建和销毁线程,并将任务分配给这些线程来执行。使用线程池可以提高程序的性能,减少线程创建和销毁的开销。 在C语言中,可以使用多线程的库来实现线程池,比如pthread库。下面是一个简单的用C语言实现线程池的步骤: 1. 定义线程池结构体:创建一个结构体来保存线程池的相关信息,如线程池的大小、任务队列、互斥锁、条件变量等。 2. 初始化线程池:在初始化函数中,需要对线程池中的各个成员进行初始化,如创建指定数量的线程、初始化互斥锁和条件变量等。 3. 定义任务函数:线程池的任务函数用于处理任务队列中的任务,根据具体需求来定义任务的执行逻辑。 4. 添加任务到线程池:当有新的任务时,将任务添加到任务队列中,并通过条件变量来通知线程池中的线程有新任务可执行。 5. 线程池中的线程获取任务并执行:在线程中循环检查任务队列,当有任务时,线程从任务队列中获取任务并执行。 6. 销毁线程池:在停止使用线程池时,要销毁线程池中的资源,包括线程的回收、互斥锁和条件变量的销毁等。 通过以上步骤,就可以在C语言中实现一个简单的线程池。具体实现中还需要考虑线程同步、任务队列的管理等问题,以确保线程池的稳定性和性能。 ### 回答3: 线程池是用来管理和复用线程的一种机制,可以更有效地使用系统资源和提高应用程序的性能。下面是使用C语言实现线程池的一般步骤: 1. 定义一个线程池的结构体,包含线程池的状态、大小、最大线程数、工作任务队列等信息。 ```c typedef struct { pthread_t *threads; // 线程数组 int thread_count; // 线程数 int max_threads; // 最大线程数 int pool_size; // 线程池大小 int shutdown; // 关闭标志 pthread_mutex_t mutex; // 互斥锁 pthread_cond_t notify; // 条件变量 Task *task_queue; // 任务队列 } ThreadPool; ``` 2. 初始化线程池,创建指定数量的线程。 ```c ThreadPool* thread_pool_init(int pool_size) { ThreadPool *pool = malloc(sizeof(ThreadPool)); pool->threads = malloc(pool_size * sizeof(pthread_t)); pool->thread_count = pool_size; pool->max_threads = pool_size; pool->pool_size = 0; pool->shutdown = 0; // 初始化互斥锁和条件变量 pthread_mutex_init(&(pool->mutex), NULL); pthread_cond_init(&(pool->notify), NULL); // 创建线程 for (int i = 0; i < pool_size; i++) { pthread_create(&(pool->threads[i]), NULL, thread_worker, (void*)pool); } return pool; } ``` 3. 定义线程工作函数,不断从任务队列中取出任务执行。 ```c void* thread_worker(void *arg) { ThreadPool *pool = (ThreadPool*)arg; while (1) { pthread_mutex_lock(&(pool->mutex)); // 线程池关闭,退出线程 while (pool->pool_size == 0 && !pool->shutdown) { pthread_cond_wait(&(pool->notify), &(pool->mutex)); } if (pool->shutdown) { pthread_mutex_unlock(&(pool->mutex)); pthread_exit(NULL); } // 从任务队列中取出任务执行 Task *task = pool->task_queue; pool->task_queue = pool->task_queue->next; pool->pool_size--; pthread_mutex_unlock(&(pool->mutex)); task->func(task->arg); free(task); } pthread_exit(NULL); } ``` 4. 定义任务结构体,包含任务函数指针和参数。 ```c typedef struct Task { void (*func)(void*); void *arg; struct Task *next; } Task; ``` 5. 向线程池中添加任务。 ```c void thread_pool_add_task(ThreadPool *pool, void (*func)(void*), void *arg) { Task *task = malloc(sizeof(Task)); task->func = func; task->arg = arg; task->next = NULL; pthread_mutex_lock(&(pool->mutex)); if (pool->task_queue == NULL) { pool->task_queue = task; } else { Task *cur = pool->task_queue; while (cur->next != NULL) { cur = cur->next; } cur->next = task; } pool->pool_size++; pthread_mutex_unlock(&(pool->mutex)); pthread_cond_signal(&(pool->notify)); } ``` 6. 关闭线程池。 ```c void thread_pool_shutdown(ThreadPool *pool) { if (pool == NULL) { return; } pthread_mutex_lock(&(pool->mutex)); pool->shutdown = 1; pthread_mutex_unlock(&(pool->mutex)); pthread_cond_broadcast(&(pool->notify)); // 等待线程退出 for (int i = 0; i < pool->thread_count; i++) { pthread_join(pool->threads[i], NULL); } // 释放资源 free(pool->threads); while (pool->task_queue != NULL) { Task *next = pool->task_queue->next; free(pool->task_queue); pool->task_queue = next; } pthread_mutex_destroy(&(pool->mutex)); pthread_cond_destroy(&(pool->notify)); free(pool); } ``` 以上是一个简单的线程池实现,通过初始化线程池、添加任务、关闭线程池等操作,可以有效地管理和复用线程,提高应用程序的性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值