简单的线程池

简单的线程池

什么是线程池?

线程池就是线程的一种使用模式。虽然线程是轻量级的进程,但是线程的创建和销毁还是会引发效率问题。并且,如果创建的线程过多,反而会增加很多的调度开销,影响系统效率。

线程池就是可以提前创建好一些线程,在我们需要使用线程的时候,对已经创建好的线程添加任务就好。

线程池的使用场景:

  • 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。

  • 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。

  • 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。

线程池原理:

这里我写的这个线程池的原理如下:

  • 创建固定数量的线程,循环从任务队列(阻塞队列)中获取任务对象。
  • 获取到任务对象后,执行任务对象中任务接口。

Code

阻塞队列:

#pragma once
#include <iostream>
#include <queue>
#include <vector>
#include <unistd.h>


// 用信号量和环形数组来实现阻塞队列
// 信号量表示可用资源个数
// 一个信号量表示当前队列中元素的个数
// 另一个信号量表示当前队列中空格的个数
// 插入元素就是在消耗一个空格资源,释放了一个元素资源
// 删除元素就是在消耗一个元素资源,释放了一个空格资源
#include <semaphore.h>

template <class T>
class BlockingQueue
{
public:
    BlockingQueue(int capacity)
        :_head(0),
        _tail(0),
        _size(0),
        _capicity(capacity)
    {
        _qu.resize(capacity);
        sem_init(&_lock, 0, 1);
        sem_init(&_elem, 0, 0);
        sem_init(&_blank, 0, _capicity);
    }

    virtual ~BlockingQueue()
    {
        sem_destroy(&_lock);
        sem_destroy(&_elem);
        sem_destroy(&_blank);
    }

    void Push(const T& data)
    {
        // 每次插入元素前,申请一个空格资源
        // 如果没有空格资源,说明队列满了
        // 满了就不能继续插入,并且在 Push 中阻塞
        sem_wait(&_blank);

        sem_wait(&_lock);
        _qu[_tail++] = data;
        _tail %= _capicity;
        _size++;
        sem_post(&_lock);

        sem_post(&_elem);
    }

    void Pop(T* data)
    {
        // 每次出队列前要申请一个元素资源
        // 如果没有元素资源,队列为空
        // 不能出队列,要在Pop 中阻塞
        sem_wait(&_elem);

        sem_wait(&_lock);
        *data = _qu[_head++];
        _head %= _capicity;
        _size--;
        sem_post(&_lock);

        sem_post(&_blank);
    }

private:
    sem_t _lock;
    std::vector<T> _qu;
    int _head;
    int _tail;
    int _size;
    int _capicity;
    sem_t _elem;
    sem_t _blank;
};

线程池

#pragma once
#include <iostream>
#include "BlockingQueue.hpp"

class Task
{
public:
    virtual void Run()
    {
        std::cout << "Base Run" << std::endl;
    }

    virtual ~Task()
    {
    }
};

// 线程池对象启动的时候会创建一组线程
// 每个线程都会完成一定的任务(执行一定的代码逻辑, 这个逻辑是由调用者来决定)
//
// 任务是一段代码,就可以用函数来表示
class ThreadPool
{
public:
    ThreadPool(int n) 
        :qu_(100),
        woker_(n)
    {
        // 创建出若干个线程
        for (int i = 0; i < n; i++)
        {
            pthread_t tid;
            pthread_create(&tid, nullptr, ThreadEntry, this);
            wokers_.push_back(tid);
        }
    }
    ~ThreadPool()
    {
        // 让线程退出,然后再回收
        for (size_t i = 0; i < wokers_.size(); i++)
        {
            pthread_cancel(wokers_[i]);
        }
        for (size_t i = 0; i < wokers_.size(); i++)
        {
            pthread_join(wokers_[i], nullptr);
        }
    }

    // 使用线程池的时候就需要由调用者加入一些任务
    // 让线程去执行
    void AddTask(Task *task)
    {
        qu_.Push(task);
    }
private:
    BlockingQueue<Task*> qu_;
    int woker_;
    std::vector<pthread_t> wokers_;

private:
    static void *ThreadEntry(void *arg)
    {
        ThreadPool *pool = (ThreadPool *)arg;

        while (true)
        {
            // 循环中尝试从阻塞队列中获取到一个任务
            // 就执行一个任务
            Task *task = nullptr;
            pool->qu_.Pop(&task);

            task->Run();
            delete task;
        }

        return nullptr;
    }
};
  • 在创建对象的时候,会创建出若干线程,并且线程会从阻塞队列中获取任务。
  • 这里有一个细节就是,如果阻塞队列为空,线程的创建也就会阻塞。所以在 Task 类中,Run函数可以定义为纯虚函数。
  • 因为任务是 new 出来的,所以线程执行完任务需要将其 delete。

Main.cpp

#include "ThreadPool.hpp"


// 这个类由用户自己定制
// 需要依赖哪些数据,都可以随意添加和修改
class MyTask : public Task
{
public:
    MyTask(int id)
        :id_(id)
    {

    }
    virtual void Run()
    {
        // 执行用户自定制的逻辑
        std::cout << "id = " << id_ << std::endl;
    }

private:
    int id_;
};

int main()
{
    ThreadPool pool(10);
    
    for (int i = 0; i < 20; i++)
    {
        pool.AddTask(new MyTask(i));
    }

    while (1)
    {
        sleep(1);
    }
    return 0;
}

这是一个非常基础的线程池,结构也比较简单,也比较好懂。

叮~🔔

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章