手写线程池 - C++版 - 笔记总结

news2024/9/29 19:29:51

1.线程池原理

创建一个线程,实现很方便。
缺点:若并发的线程数量很多,并且每个线程都是执行一个时间较短的任务就结束了。
      由于频繁的创建线程和销毁线程需要时间,这样的频繁创建线程会大大降低
      系统的效率。

2.思考 :怎么样实现线程复用,让线程执行完一个任务后不被销毁,还可以继续执行其他的任务?

答:线程池

3.思考 :什么是线程池?

可以点击看爱编程的大丙写的这边文章!写的很好

 4.线程池的组成:任务队列、工作的线程、管理者线程

(1)任务队列
听到任务队列之后,咱们在脑海里应该闪现出另外一个模型,就是生产者和消费者模型。因为只要
有任务队列,就离不开生产者和消费者模型。为什么要有任务队列呢?就是因为他有生产者要把生
产出来的数据或者商品进行存储,存储起来之后让对应的某些消费者去消费。在我们写程序的时候,
就是有一些生产者线程还是负责往这个任务队列里边放任务,然后有一些消费者线程负责把任务从
任务队列中取出去,并且把它处理掉。在线程池里边有任务队列,也就意味着这个线程池它是生产者
和消费者模型里边的一部分。哪一个部分呢?这个线程池主要是负责维护一个任务队列,然后再维护
若干个消费者线程。它维护的线程都是消费者线程。
那么生产者是谁呢?谁使用任务队列,谁就是生产者。那么这个生产者它把任务放到任务队列里边。
怎么放呢?肯定是通过线程池提供的api接口,把这个任务放到任务队列,,放进来之后,这个消费
者线程通过一个循环,不停地从这个任务队列里边去取任务,
假设说咱们这个任务队列为空了,消费者线程就需要阻塞了。可以使用条件变量把它阻塞掉就行了。
如果让消费者线程去阻塞,它就放弃了CPU时间片了,这个对系统的资源消耗是一点都没有的。
对于生产者来说,假设这个任务队列已经满了,需要阻塞生产者。当这个消费者消费了这个产品之后,
任务队列就不再满了,那么任务队列不再满之后,咱们就让消费者把这个生产者唤醒,让他们继续生
产。这个任务队列是两个角色,而不是三个角色,这一点要搞明白的!!!
 
 
 
(2)工作的线程
这个任务队列里边的任务都是函数地址,工作的线程在处理这个任务的时候,它就基于这个函数地址
对那个函数进行调用。也就说这个任务队列里边的任务都是回调函数。
什么时候去回调呢?
就是当线程池里边的这个消费者线程把它取出去之后,它就被调用了。如果说没有被取出来,他就不
被调用了。
 
 
(3)管理者线程
管理者线程的职责非常单一,就是不停的去检测当前任务队列里边任务的个数,还有当前工作的线程
的线程数,然后针对于它们的数量进行一个运算。看一看现在需要添加线程还是销毁线程。干这个事
的时候,可以给它设置一个频率,比如说你可以让他五秒钟去做一次或者十秒钟去做一次。sleep管理
者是非常轻松的

(一)任务类Task的定义

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

using callback = void (*)(void *);

// 任务结构体
template <typename T>
struct Task
{
    Task<T>()
    {
        function = nullptr;
        arg = nullptr;
    }
    Task<T>(callback f, void *arg)
    {
        this->arg = (T *)arg;
        function = f;
    }
    callback function;
    T *arg;
};

(二)任务队列TaskQueue的定义

template <typename T>
class TaskQueue
{
public:
    TaskQueue();
    ~TaskQueue();

    // 添加任务
    void addTask(Task<T> task);
    void addTask(callback f, void *arg);

    // 取出一个任务
    Task<T> takeTask();
    // 获取当前任务的个数
    inline size_t taskNumber()
    {
        return m_taskQ.size();
    }

private:
    std::queue<Task<T> > m_taskQ;
    pthread_mutex_t m_mutex;
};

(三)线程池 ThreadPool 类的声明

#pragma once
#include "TaskQueue.h"
template <typename T>
class ThreadPool
{
public:
    // 创建线程池并初始化
    ThreadPool(int min, int max);

    // 销毁线程池
    ~ThreadPool();

    // 给线程池添加任务
    void addTask(Task<T> task);

    // 获取线程池中工作的线程的个数
    int getBusyNum();

    // 获取线程池中活着的线程个数
    int getAliveNum();

private:
    // 工作的线程是(消费者线程)任务函数
    static void *worker(void *arg);
    // 管理者线程任务函数
    static void *manager(void *arg);
    // 单个线程退出
    void threadExit();

private:
    // 任务队列
    TaskQueue<T> *taskQ;

    pthread_t managerID;       // 管理者线程ID
    pthread_t *threadIDs;      // 工作的线程ID
    int minNum;                // 最小线程数量
    int maxNum;                // 最大线程数量
    int busyNum;               // 忙的线程的个数
    int liveNum;               // 存活的线程的个数
    int exitNum;               // 要销毁的线程个数
    pthread_mutex_t mutexPool; // 锁整个的线程池
    pthread_cond_t notEmpty;   // 任务队列是不是空了

    bool shutdown = false; // 是不是要销毁线程池
    static const int NUMBER = 2;
};

(四)线程池 构造函数

template <typename T>
ThreadPool<T>::ThreadPool(int min, int max)
{
    do
    {
        // 实例化任务队列
        taskQ = new TaskQueue<T>;
        if (taskQ == nullptr)
        {
            std::cout << "new taskQ fail..." << std::endl;
            break;
        }

        // 根据线程的最大上限给线程数组分配内存
        threadIDs = new pthread_t[max];
        if (threadIDs == nullptr)
        {
            std::cout << "new threadIDs fail..." << std::endl;
            break;
        }
        // 初始化
        memset(threadIDs, 0, sizeof(pthread_t) * max);
        minNum = min;
        maxNum = max;
        busyNum = 0;
        liveNum = min; // 和最小个数相等
        exitNum = 0;

        // 初始化互斥锁,条件变量
        if (pthread_mutex_init(&mutexPool, NULL) != 0 ||
            pthread_cond_init(&notEmpty, NULL) != 0)
        {
            std::cout << "mutex or condition init fail...\n"
                      << std::endl;
            break;
        }

        shutdown = false;
        /// 创建线程 //
        //"void *(ThreadPool::*)(void *arg)" 类型的实参与 "void *(*)(void *)" 类型的形参不兼容
        // 类的静态成员或者类的外部的非类普通函数,只要定义出来之后就有地址
        // 如果是类的成员函数,并且不是静止的,这个函数定义出来是没有地址的
        // 什么时候有地址呢?我们必须要给这个类进行实例化,创建类的对象,这个
        // 函数才有地址
        // this指针指向当前被实例化的对象,如果要是在外边new出来一个ThreadPool对象,
        // 那么这个this指针就指向该实例化对象
        // 为什么要把this传给manager呢?
        // 因为manager是一个静态方法,静态方法它只能访问类里边的静态成员变量,
        // 它不能访问类的非静态成员变量。
        // 因此如果我们想要访问这些非静态成员变量,就必须要给这个静态方法传进去一个
        // 实例化对象,通过传进去的这个实例化对象来访问里边的非静态成员函数和变量

        // 创建管理者线程,1个
        pthread_create(&managerID, NULL, manager, this);
        // 根据最小线程个数,创建线程
        for (int i = 0; i < min; i++)
        {
            pthread_create(&threadIDs[i], NULL, worker, this);
        }
        return;
    } while (0);
    // 释放资源
    if (threadIDs)
        delete[] threadIDs;
    if (taskQ)
        delete taskQ;
}

(五)工作的线程的任务函数

// 工作线程任务函数
template <typename T>
void *ThreadPool<T>::worker(void *arg)
{
    ThreadPool *pool = static_cast<ThreadPool *>(arg);
    // 一直不停的工作
    while (true)
    {
        // 访问任务队列(共享资源)加锁
        pthread_mutex_lock(&pool->mutexPool);
        // 当前任务队列是否为空,如果为空工作线程阻塞
        while (pool->taskQ->taskNumber() == 0 && !pool->shutdown)
        {
            //printf("thread %ld waiting...\n",pthread_self());
            // 阻塞工作线程
            pthread_cond_wait(&pool->notEmpty, &pool->mutexPool);
            // 解除阻塞之后,判断是不是要销毁线程
            if (pool->exitNum > 0)
            {
                pool->exitNum--;
                if (pool->liveNum > pool->minNum)
                {
                    pool->liveNum--;
                    pthread_mutex_unlock(&pool->mutexPool);
                    pool->threadExit();
                }
            }
        }
        // 判断线程池是否被关闭了
        if (pool->shutdown)
        {
            pthread_mutex_unlock(&pool->mutexPool);
            pool->threadExit();
        }
        // 从任务队列中取出一个任务
        Task<T> task = pool->taskQ->takeTask();
        // 工作的线程+1
        pool->busyNum++;
        // 线程池解锁
        pthread_mutex_unlock(&pool->mutexPool);

        // 执行任务
        printf("thread %ld start working...\n", pthread_self());
        // std::cout<<"thread "<<to_string(pthread_self()) <<" start working..."<<std::endl;

        task.function(task.arg);
        delete task.arg;
        task.arg = nullptr;

        // 任务处理结束
        printf("thread %ld end working...\n", pthread_self());
        // std::cout<<"thread "<<std::string to_string(pthread_self()) <<" end working..."<<std::endl;

        pthread_mutex_lock(&pool->mutexPool);
        pool->busyNum--;
        pthread_mutex_unlock(&pool->mutexPool);
    }
    return nullptr;
}

(六)线程退出函数

//线程退出
template <typename T>
void ThreadPool<T>::threadExit()
{
    pthread_t tid = pthread_self();
    for (int i = 0; i < maxNum; ++i)
    {
        if (threadIDs[i] == tid)
        {
            threadIDs[i] = 0;
            // std::cout<<"threadExit() called, "<<std::string to_string(tid)<<" exiting..."<<std::endl;
            printf("threadExit() called,%ld exiting...\n", tid);
            break;
        }
    }
    pthread_exit(NULL); // 这个是标准C的函数
}

(七)管理者线程的任务函数

// 管理者线程任务函数
template <typename T>
void *ThreadPool<T>::manager(void *arg)
{
    ThreadPool *pool = static_cast<ThreadPool *>(arg);
    // 如果线程池没有关闭,就一直检测
    while (!pool->shutdown)
    {
        // 每隔3s检测一次
        sleep(3);

        // 取出线程池中任务的数量和当前线程的数量 取出(工作的)忙的线程的数量
        pthread_mutex_lock(&pool->mutexPool);
        int queueSize = pool->taskQ->taskNumber();
        int liveNum = pool->liveNum;
        int busyNum = pool->busyNum;
        pthread_mutex_unlock(&pool->mutexPool);

        // 添加线程
        // 任务的个数>存货的线程个数 && 存活的线程数 < 最大线程数
        if (queueSize > liveNum && liveNum < pool->maxNum)
        {
            pthread_mutex_lock(&pool->mutexPool);
            int counter = 0;
            for (int i = 0; i < pool->maxNum && counter < NUMBER && pool->liveNum < pool->maxNum; ++i)
            {
                if (pool->threadIDs[i] == 0)
                {
                    pthread_create(&pool->threadIDs[i], NULL, worker, pool);
                    counter++;
                    pool->liveNum++;
                }
            }
            pthread_mutex_unlock(&pool->mutexPool);
        }

        // 销毁多余的线程
        // 忙的线程 * 2 < 存活的线程数 && 存活的线程 > 最小线程数
        if (busyNum * 2 < liveNum && liveNum > pool->minNum)
        {
            pthread_mutex_lock(&pool->mutexPool);
            pool->exitNum = NUMBER;
            pthread_mutex_unlock(&pool->mutexPool);
            // 让工作的线程自杀
            for (int i = 0; i < NUMBER; ++i)
            {
                pthread_cond_signal(&pool->notEmpty);
            }
        }
    }
    return nullptr;
}

(八)给线程池添加任务

// 给线程池添加任务
template <typename T>
void ThreadPool<T>::addTask(Task<T> task)
{
    if (shutdown)
    {
        return;
    }
    // 添加任务,不需要加锁,任务队列中有锁
    taskQ->addTask(task);
    // 唤醒工作的线程
    pthread_cond_signal(&notEmpty); // 通知消费者消费
}

(九)获取线程池忙的线程个数和活着的线程个数

template <typename T>
int ThreadPool<T>::getBusyNum()
{
    pthread_mutex_lock(&mutexPool);
    int busyNum = this->busyNum;
    pthread_mutex_unlock(&mutexPool);
    return busyNum;
}

template <typename T>
int ThreadPool<T>::getAliveNum()
{
    pthread_mutex_lock(&mutexPool);
    int aliveNum = this->liveNum;
    pthread_mutex_unlock(&mutexPool);
    return aliveNum;
}

(十)线程池析构

template <typename T>
ThreadPool<T>::~ThreadPool()
{
    // 关闭线程池
    shutdown = true;
    // 阻塞回收管理者线程
    pthread_join(managerID, NULL);
    // 唤醒阻塞的消费者线程
    for (int i = 0; i < liveNum; i++)
    {
        pthread_cond_signal(&notEmpty);
    }
    // 释放堆内存
    if (taskQ) delete taskQ;
    if (threadIDs) delete[] threadIDs;
    pthread_mutex_destroy(&mutexPool);
    pthread_cond_destroy(&notEmpty);
}

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

完整代码:

TaskQueue.h

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

using callback = void (*)(void *);

// 任务结构体
template <typename T>
struct Task
{
    Task<T>()
    {
        function = nullptr;
        arg = nullptr;
    }
    Task<T>(callback f, void *arg)
    {
        this->arg = (T *)arg;
        function = f;
    }
    callback function;
    T *arg;
};

template <typename T>
class TaskQueue
{
public:
    TaskQueue();
    ~TaskQueue();

    // 添加任务
    void addTask(Task<T> task);
    void addTask(callback f, void *arg);

    // 取出一个任务
    Task<T> takeTask();
    // 获取当前任务的个数
    inline size_t taskNumber()
    {
        return m_taskQ.size();
    }

private:
    std::queue<Task<T> > m_taskQ;
    pthread_mutex_t m_mutex;
};

TaskQueue.cpp

#include "TaskQueue.h"
#include <pthread.h>

template <typename T>
TaskQueue<T>::TaskQueue()
{
    // 初始化互斥锁
    pthread_mutex_init(&m_mutex, NULL);
}

template <typename T>
TaskQueue<T>::~TaskQueue()
{
    // 销毁互斥锁
    pthread_mutex_destroy(&m_mutex);
}

template <typename T>
void TaskQueue<T>::addTask(Task<T> task)
{
    pthread_mutex_lock(&m_mutex);
    m_taskQ.push(task);
    pthread_mutex_unlock(&m_mutex);
}

template <typename T>
Task<T> TaskQueue<T>::takeTask()
{
    Task<T> t;
    pthread_mutex_lock(&m_mutex);
    if (!m_taskQ.empty())
    {
        t = m_taskQ.front();
        m_taskQ.pop();
    }
    pthread_mutex_unlock(&m_mutex);
    return t;
}

template <typename T>
void TaskQueue<T>::addTask(callback f, void *arg)
{
    pthread_mutex_lock(&m_mutex);
    m_taskQ.push(Task<T>(f, arg));
    pthread_mutex_unlock(&m_mutex);
}

ThreadPool.h

#pragma once
#include "TaskQueue.h"
template <typename T>
class ThreadPool
{
public:
    // 创建线程池并初始化
    ThreadPool(int min, int max);

    // 销毁线程池
    ~ThreadPool();

    // 给线程池添加任务
    void addTask(Task<T> task);

    // 获取线程池中工作的线程的个数
    int getBusyNum();

    // 获取线程池中活着的线程个数
    int getAliveNum();

private:
    // 工作的线程是(消费者线程)任务函数
    static void *worker(void *arg);
    // 管理者线程任务函数
    static void *manager(void *arg);
    // 单个线程退出
    void threadExit();

private:
    // 任务队列
    TaskQueue<T> *taskQ;

    pthread_t managerID;       // 管理者线程ID
    pthread_t *threadIDs;      // 工作的线程ID
    int minNum;                // 最小线程数量
    int maxNum;                // 最大线程数量
    int busyNum;               // 忙的线程的个数
    int liveNum;               // 存活的线程的个数
    int exitNum;               // 要销毁的线程个数
    pthread_mutex_t mutexPool; // 锁整个的线程池
    pthread_cond_t notEmpty;   // 任务队列是不是空了

    bool shutdown = false; // 是不是要销毁线程池
    static const int NUMBER = 2;
};

ThreadPool.cpp

#include "ThreadPool.h"
#include <iostream>
#include <string.h>
#include <stdlib.h>

#include <unistd.h>

template <typename T>
ThreadPool<T>::ThreadPool(int min, int max)
{
    do
    {
        // 实例化任务队列
        taskQ = new TaskQueue<T>;
        if (taskQ == nullptr)
        {
            std::cout << "new taskQ fail..." << std::endl;
            break;
        }

        // 根据线程的最大上限给线程数组分配内存
        threadIDs = new pthread_t[max];
        if (threadIDs == nullptr)
        {
            std::cout << "new threadIDs fail..." << std::endl;
            break;
        }
        // 初始化
        memset(threadIDs, 0, sizeof(pthread_t) * max);
        minNum = min;
        maxNum = max;
        busyNum = 0;
        liveNum = min; // 和最小个数相等
        exitNum = 0;

        // 初始化互斥锁,条件变量
        if (pthread_mutex_init(&mutexPool, NULL) != 0 ||
            pthread_cond_init(&notEmpty, NULL) != 0)
        {
            std::cout << "mutex or condition init fail...\n"
                      << std::endl;
            break;
        }

        shutdown = false;
        /// 创建线程 //
        //"void *(ThreadPool::*)(void *arg)" 类型的实参与 "void *(*)(void *)" 类型的形参不兼容
        // 类的静态成员或者类的外部的非类普通函数,只要定义出来之后就有地址
        // 如果是类的成员函数,并且不是静止的,这个函数定义出来是没有地址的
        // 什么时候有地址呢?我们必须要给这个类进行实例化,创建类的对象,这个
        // 函数才有地址
        // this指针指向当前被实例化的对象,如果要是在外边new出来一个ThreadPool对象,
        // 那么这个this指针就指向该实例化对象
        // 为什么要把this传给manager呢?
        // 因为manager是一个静态方法,静态方法它只能访问类里边的静态成员变量,
        // 它不能访问类的非静态成员变量。
        // 因此如果我们想要访问这些非静态成员变量,就必须要给这个静态方法传进去一个
        // 实例化对象,通过传进去的这个实例化对象来访问里边的非静态成员函数和变量

        // 创建管理者线程,1个
        pthread_create(&managerID, NULL, manager, this);
        // 根据最小线程个数,创建线程
        for (int i = 0; i < min; i++)
        {
            pthread_create(&threadIDs[i], NULL, worker, this);
        }
        return;
    } while (0);
    // 释放资源
    if (threadIDs)
        delete[] threadIDs;
    if (taskQ)
        delete taskQ;
}

// 工作线程任务函数
template <typename T>
void *ThreadPool<T>::worker(void *arg)
{
    ThreadPool *pool = static_cast<ThreadPool *>(arg);
    // 一直不停的工作
    while (true)
    {
        // 访问任务队列(共享资源)加锁
        pthread_mutex_lock(&pool->mutexPool);
        // 当前任务队列是否为空,如果为空工作线程阻塞
        while (pool->taskQ->taskNumber() == 0 && !pool->shutdown)
        {
            //printf("thread %ld waiting...\n",pthread_self());
            // 阻塞工作线程
            pthread_cond_wait(&pool->notEmpty, &pool->mutexPool);
            // 解除阻塞之后,判断是不是要销毁线程
            if (pool->exitNum > 0)
            {
                pool->exitNum--;
                if (pool->liveNum > pool->minNum)
                {
                    pool->liveNum--;
                    pthread_mutex_unlock(&pool->mutexPool);
                    pool->threadExit();
                }
            }
        }
        // 判断线程池是否被关闭了
        if (pool->shutdown)
        {
            pthread_mutex_unlock(&pool->mutexPool);
            pool->threadExit();
        }
        // 从任务队列中取出一个任务
        Task<T> task = pool->taskQ->takeTask();
        // 工作的线程+1
        pool->busyNum++;
        // 线程池解锁
        pthread_mutex_unlock(&pool->mutexPool);

        // 执行任务
        printf("thread %ld start working...\n", pthread_self());
        // std::cout<<"thread "<<to_string(pthread_self()) <<" start working..."<<std::endl;

        task.function(task.arg);
        delete task.arg;
        task.arg = nullptr;

        // 任务处理结束
        printf("thread %ld end working...\n", pthread_self());
        // std::cout<<"thread "<<std::string to_string(pthread_self()) <<" end working..."<<std::endl;

        pthread_mutex_lock(&pool->mutexPool);
        pool->busyNum--;
        pthread_mutex_unlock(&pool->mutexPool);
    }
    return nullptr;
}

//线程退出
template <typename T>
void ThreadPool<T>::threadExit()
{
    pthread_t tid = pthread_self();
    for (int i = 0; i < maxNum; ++i)
    {
        if (threadIDs[i] == tid)
        {
            threadIDs[i] = 0;
            // std::cout<<"threadExit() called, "<<std::string to_string(tid)<<" exiting..."<<std::endl;
            printf("threadExit() called,%ld exiting...\n", tid);
            break;
        }
    }
    pthread_exit(NULL); // 这个是标准C的函数
}

// 管理者线程任务函数
template <typename T>
void *ThreadPool<T>::manager(void *arg)
{
    ThreadPool *pool = static_cast<ThreadPool *>(arg);
    // 如果线程池没有关闭,就一直检测
    while (!pool->shutdown)
    {
        // 每隔3s检测一次
        sleep(3);

        // 取出线程池中任务的数量和当前线程的数量 取出(工作的)忙的线程的数量
        pthread_mutex_lock(&pool->mutexPool);
        int queueSize = pool->taskQ->taskNumber();
        int liveNum = pool->liveNum;
        int busyNum = pool->busyNum;
        pthread_mutex_unlock(&pool->mutexPool);

        // 添加线程
        // 任务的个数>存货的线程个数 && 存活的线程数 < 最大线程数
        if (queueSize > liveNum && liveNum < pool->maxNum)
        {
            pthread_mutex_lock(&pool->mutexPool);
            int counter = 0;
            for (int i = 0; i < pool->maxNum && counter < NUMBER && pool->liveNum < pool->maxNum; ++i)
            {
                if (pool->threadIDs[i] == 0)
                {
                    pthread_create(&pool->threadIDs[i], NULL, worker, pool);
                    counter++;
                    pool->liveNum++;
                }
            }
            pthread_mutex_unlock(&pool->mutexPool);
        }

        // 销毁多余的线程
        // 忙的线程 * 2 < 存活的线程数 && 存活的线程 > 最小线程数
        if (busyNum * 2 < liveNum && liveNum > pool->minNum)
        {
            pthread_mutex_lock(&pool->mutexPool);
            pool->exitNum = NUMBER;
            pthread_mutex_unlock(&pool->mutexPool);
            // 让工作的线程自杀
            for (int i = 0; i < NUMBER; ++i)
            {
                pthread_cond_signal(&pool->notEmpty);
            }
        }
    }
    return nullptr;
}

// 给线程池添加任务
template <typename T>
void ThreadPool<T>::addTask(Task<T> task)
{
    if (shutdown)
    {
        return;
    }
    // 添加任务,不需要加锁,任务队列中有锁
    taskQ->addTask(task);
    // 唤醒工作的线程
    pthread_cond_signal(&notEmpty); // 通知消费者消费
}

template <typename T>
int ThreadPool<T>::getBusyNum()
{
    pthread_mutex_lock(&mutexPool);
    int busyNum = this->busyNum;
    pthread_mutex_unlock(&mutexPool);
    return busyNum;
}

template <typename T>
int ThreadPool<T>::getAliveNum()
{
    pthread_mutex_lock(&mutexPool);
    int aliveNum = this->liveNum;
    pthread_mutex_unlock(&mutexPool);
    return aliveNum;
}

template <typename T>
ThreadPool<T>::~ThreadPool()
{
    // 关闭线程池
    shutdown = true;
    // 阻塞回收管理者线程
    pthread_join(managerID, NULL);
    // 唤醒阻塞的消费者线程
    for (int i = 0; i < liveNum; i++)
    {
        pthread_cond_signal(&notEmpty);
    }
    // 释放堆内存
    if (taskQ) delete taskQ;
    if (threadIDs) delete[] threadIDs;
    pthread_mutex_destroy(&mutexPool);
    pthread_cond_destroy(&notEmpty);
}

main.cpp

#include <iostream>
#include <pthread.h>
#include "ThreadPool.h"
#include "ThreadPool.cpp"
#include "TaskQueue.cpp"
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

void taskFunc(void *arg)
{
    int num = *(int *)arg;
    printf("thread %ld is working, number = %d \n", pthread_self(), num);
    sleep(1);
}

int main()
{
    // 创建线程池
    ThreadPool<int> pool(3, 10);
    for (int i = 0; i < 100; ++i)
    {
        int *num = new int(i + 100);
        *num = i + 100;
        pool.addTask(Task<int>(taskFunc, num));
    }
    sleep(20);
    return 0;
}

跟着这个老师的教程学习的,总结的笔记!!! 

手写线程池 - C改C++版 | 爱编程的大丙 (subingwen.cn)icon-default.png?t=N6B9https://subingwen.cn/linux/threadpool-cpp/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/808101.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Python从入门到进阶】30、JSONPath的介绍和使用

接上篇《29、xpath抓取站长素材图片》 上一篇我们讲解了如何利用xpath来抓取站长素材网站的图片区首页的所有图片&#xff0c;本篇我们来介绍JSONPath的基础和具体使用。 一、JSONPath的基本概念 1、什么是JSONPath&#xff1f; JSONPath是一种用于在JSON&#xff08;JavaSc…

致敬图灵!HashData拥抱数据智能新时代!

图1&#xff1a;2023ACM中国图灵大会现场 生于1912年的艾伦图灵被称为“计算机科学之父”、“人工智能之父”。1966年&#xff0c;国际计算机协会&#xff08;ACM&#xff09;为了纪念这位卓越的科学家&#xff0c;设立了以其名字命名的ACM图灵奖&#xff0c;以表彰在计算机领…

RH850 1372/1374 程序跑飞异常分析

文章目录 前言现象描述原因分析解决方案总结 前言 最近项目用瑞萨RH850系列的1372/1374开发&#xff0c;官方的MCAL做的不咋地就算了&#xff0c;FAE支持也很少。给的demo问题也很多。本文记录一下开发过程中的问题。 现象描述 MCAL配置完ADC1后&#xff0c;运行ADC1的采样程…

SQL注入原理分析

前言 order by的作用及含义 order by 用于判断显示位&#xff0c;order by 原有的作用是对字段进行一个排序&#xff0c;在sql注入中用order by 来判断排序&#xff0c;order by 1就是对一个字段进行排序&#xff0c;如果一共四个字段&#xff0c;你order by 5 数据库不知道怎么…

51:电机(ULN2003D)

1:介绍 我们51单片机使用的是直流电机 直流电机是一种将电能转换为机械能的装置。一般的直流电机有两个电极&#xff0c;当电极正接时&#xff0c;电机正转&#xff0c;当电极反接时&#xff0c;电机反转 直流电机主要由永磁体&#xff08;定子&#xff09;、线圈&#xff08;转…

【Java|基础篇】File类和IO流

文章目录 1.File类2.流的概念3.InputStream4.OutputStream5.Reader6.Writer7.使用Scanner读文件8.使用PrintWriter写文件9.close()方法10.flush()方法10.总结 1.File类 File类是Java中用于表示文件或目录的类。它提供了一些方法来操作文件和目录的属性和内容&#xff0c;可以进…

对js中的window深入理解

window和 document的区别 window对象是浏览器中的全局对象&#xff0c;代表的是整个浏览器窗口&#xff1b;document只是window对象中的一部分&#xff0c;表示当前窗口或框架中加载的HTML文档&#xff0c;主要用于访问和操作文档的内容&#xff0c;包括DOM元素、样式、事件等&…

华为OD机试真题 Java 实现【AI面板识别】【2023 B卷 100分】,附详细解题思路

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明4、控制台输出 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08…

【Javalin 】Javalin Kotlin/Java 轻量级 REST API 库

Javalin 旨在为 Kotlin 和 Java 提供一个易用的轻量级 REST API 库。这个 REST API 易于使用&#xff0c;API 也非常的流畅。 Javalin 主要有以下的特点&#xff1a; 易用&#xff1a;不用提前学习任何概念就可以开始使用 一致的 API&#xff1a;所有的处理程序和映射器在 Co…

基于RK3588+AI的边缘计算算法方案:智慧园区、智慧社区、智慧物流

RK3588 AI 边缘计算主板规格书简介 关于本文档 本文档详细介绍了基于Rockchip RK3588芯片的AI边缘计算主板外形、尺寸、技术规格&#xff0c;以及详细的硬件接口设计参考说明&#xff0c;使客户可以快速将RK3588边缘计算主板应用于工业互联网、智慧城市、智慧安防、智慧交通&am…

年轻人的第一套海景房

前段时间新房装修&#xff0c;我把书房设计成工作室的风格&#xff0c;并自己装配了一台电脑&#xff0c;本文是对电脑选购与装配的一则经验贴&#xff0c;仅包含我对计算机硬件的浅薄理解。 配件选购 装机契源 事实上&#xff0c;很多电脑店都提供装配和测试服务&#xff0c…

【二叉树】刷题二(以递归写法为主)

617. 合并二叉树 class Solution:def mergeTrees(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> Optional[TreeNode]:root TreeNode()if not root1 and not root2:returnelif root1 and not root2:root root1elif not root1 and root2:root root2elif…

AD21原理图的高级应用(一)端口的应用

&#xff08;一&#xff09;端口的应用 1.放置端口2.自动给端口添加页码 说明&#xff0c;博主的AD版本是AD21&#xff0c;所有的工程都基于AD21&#xff0c;虽然AD软件几乎不存在版本兼容性问题&#xff0c;但还是建议大家使用对应版本的软件来学习。 资料仅供学习使用。 1.…

013 怎么查看自己电脑的wifi密码

方法一&#xff1a;查看当前电脑连接的无线密码 步骤1&#xff1a; 打开windows命令行窗口&#xff0c;输入&#xff1a;ncpa.cpl 快速打开“控制面板”中的“网络连接”&#xff0c;如下图&#xff1a; 步骤2&#xff1a; 右键&#xff0c;打开“状态” 步骤3&#xff1a;…

Pearson correlation皮尔逊相关性分析

在参数检验的相关性分析方法主要是皮尔逊相关&#xff08;Pearson correlation&#xff09;。既然是参数检验方法&#xff0c;肯定是有一些前提条件。皮尔逊相关的前提是必须满足以下几个条件&#xff1a; 变量是连续变量&#xff1b;比较的两个变量必须来源于同一个总体&…

瓦瑟斯坦距离、收缩映射和现代RL理论

Wasserstein Distance, Contraction Mapping, and Modern RL Theory | by Kowshik chilamkurthy | Medium 一、说明 数学家们在考虑一些应用的情况下探索的概念和关系 - 几十年后成为他们最初从未想象过的问题的意想不到的解决方案。 黎曼的几何学只是出于纯粹的原因才被发现的…

Abaqus 导出单元刚度矩阵和全局刚度矩阵

Abaqus 导出单元刚度矩阵和全局刚度矩阵 首次创建&#xff1a;2023.7.29 最后更新&#xff1a;2023.7.29 如有什么改进的地方&#xff0c;欢迎大家讨论&#xff01; 详细情况请查阅&#xff1a;Abaqus Analysis User’s Guide 一、Abaqus 导出单元刚度矩阵 1.生成单元刚度矩阵…

Kafka原理剖析

一、简介 Kafka是一个分布式的、分区的、多副本的消息发布-订阅系统&#xff0c;它提供了类似于JMS的特性&#xff0c;但在设计上完全不同&#xff0c;它具有消息持久化、高吞吐、分布式、多客户端支持、实时等特性&#xff0c;适用于离线和在线的消息消费&#xff0c;如常规的…

安装Anaconda3和MiniConda3

MiniConda3官方版是一款优秀的Python环境管理软件。MiniConda3最新版只包含conda及其依赖项如果您更愿意拥有conda以及超过720个开源软件包&#xff0c;请安装Anaconda。MiniConda3官方版还是一个开源的软件包管理系统和环境管理系统&#xff0c;能够帮助用户安装多个版本的软件…

Android高德地图定位实现签到打卡功能(全网最详细+收藏)

前言 本章根据高德地图API&#xff0c;实现打卡签到功能。用到了定位SDK 和地图SDK、覆盖物。打卡范围图形可以支持多种形状&#xff0c;如&#xff1a;圆形、长方形、多边形。 核心逻辑&#xff1a; 获取当前定位信息&#xff0c;然后通过Marker绘制小图标进行展示&a…