个人技术分享

42 线程池

一种线程使用模式,线程过多会带来调度开销,进而影响缓存局部性和整体性能。线程池维护多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价,线程池不仅能保证内核的充分利用,还能防止过分调度,可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量

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

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

在这里插入图片描述

提前申请很多线程,线程处于睡眠状态,当往共享区放入任务后唤起线程拿到任务开始运行。和生产消费类似

类的设计

用一个数组保存所有线程,元素类型是自定义线程类,包含线程tid和名字。队列作为共享数据。一个锁保护共享数据的安全,条件变量保证多个线程访问的同步。start功能创建指定数量的线程,运行指定函数,类内函数默认第一个参数是this指针,但线程函数必须是void*参数,所以这个函数加static。加锁访问,当共享资源为空时线程挂起,否则取出队首的元素交给线程执行,执行前释放锁。放入任务功能,加锁访问,放入成功后唤醒休眠线程

#pragma once
#include <vector>
#include <queue>
#include <pthread.h>
#include <string>
#include <unistd.h>

//换为封装的线程
struct ThreadInfo
{
    pthread_t _tid;
    std::string _name;
};
template <class T>
class pool
{
    static const int defaultnum = 5;

public:
    pool(int num = defaultnum)
    :_thread(num)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }

    std::string getname(pthread_t tid)
    {
        for (auto ch : _thread)
        {
            if (ch._tid == tid)
            {
                return ch._name;
            }
        }
        return "None";
    }
    static void* HandlerTask(void* args)
    {
        pool<T> *tp = static_cast<pool<T> *>(args);
        std::string name = tp->getname(pthread_self());
        while (true)
        {
            pthread_mutex_lock(&(tp->_mutex));
            while (tp->_que.empty())
            {
                pthread_cond_wait(&(tp->_cond), &(tp->_mutex));
            }
            T t = tp->_que.front();
            tp->_que.pop();
            pthread_mutex_unlock(&tp->_mutex);
            t.run();
            printf("%s finsih task:%s\n", name.c_str(), t.getresult().c_str());
            sleep(1);
        }
        }
    void start()
    {
        for (int i = 0; i < _thread.size(); i++)
        {
            _thread[i]._name = "thread" + std::to_string(i);
            pthread_create(&_thread[i]._tid, nullptr, HandlerTask, this);
        }
    }

    void push(const T& x)
    {
        pthread_mutex_lock(&_mutex);
        _que.push(x);
        pthread_cond_signal(&_cond);
        pthread_mutex_unlock(&_mutex);
    }
    ~pool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }
private:
    std::vector<ThreadInfo> _thread;
    std::queue<T> _que;

    pthread_mutex_t _mutex;
    pthread_cond_t _cond;
};

task.hpp

#pragma once
#include <stdio.h>
#include <string>

enum
{
    DIVZERO = 1,
    UNKNOW
};
std::string g_op = "+-*/";
struct task
{
public:
    task()
    {}
    task(int a, int b, char op)
    :_a(a), _b(b), _op(op), _result(0), _exitcode(0)
    {}

    void run()
    {
        switch(_op)
        {
            case '+':
                _result = _a + _b;
                break;
            case '-':
                _result = _a - _b;
                break;
            case '*':
                _result = _a * _b;
                break;
            case '/':
                if (_b == 0)
                {
                    _exitcode = DIVZERO;
                }
                else
                {
                     _result = _a / _b;
                }      
                break;
            default:
                _exitcode = UNKNOW;
                break;
            }
        //printf("%d+%d结果:%d\n", _a, _b, _a + _b);
    }

    std::string getresult()
    {
        std::string str = std::to_string(_a) + _op  + std::to_string(_b);
        str += "=";
        str += std::to_string(_result);
        str += " [exit:";
        str += std::to_string(_exitcode);
        str += "]";

        return str;
    }

    std::string gettask()
    {
        std::string str = std::to_string(_a) + _op  + std::to_string(_b);
        return str;
    }

private:
    int _a;
    int _b;
    char _op;

    int _result;
    int _exitcode;
};

main.cc

#include <stdlib.h>
#include <thread>
#include "task.hpp"
#include "ThreadPool.hpp"
#include "Thread.hpp"


int main()
{
     srand(time(NULL));
     printf("run\n");
     pool<task> *pl = new pool<task>(5);

     pl->start();
     sleep(3);
     while (true)
     { 
       // 构建任务
      int data1 = rand() % 10;
       int data2 = rand() % 10 + 1;
       char op = g_op[rand() % 4];
       task t(data1, data2, op);
       // 交给线程池
       printf("task:%s\n", t.gettask().c_str());
       pl->push(t);
       sleep(1);
     }
}

线程类封装

c++11引入了线程类,对底层线程函数做了封装,仍然离不开lpthread库,对原生线程也试着封装,引入自己的功能

线程类保存tid,名字,是否运行,启动时间,需要调用的函数
通过函数指针设置为类成员变量需要调用的函数,生成的线程先调用接口函数,接口函数调用入口函数,入口函数调用函数指针指向的函数。可以通过类模板作为成员变量来引入需要传入的参数

#pragma once
#include <string>
#include <ctime>
#include <pthread.h>

static int num = 1;
typedef void (*callback)();
//模板传参数
class Thread
{
public:
    Thread(callback cb)
    :_tid(0), _name(""), _time(0), _running(false), _cb(cb)
    {}

    static void* routine(void* args)
    {
        Thread *tp = static_cast<Thread *>(args);
        tp->entry();
        return nullptr;
    }

    void run()
    {
        _name = "thread-" + std::to_string(num++);
        _time = time();
        _running = true;
        pthread_create(&_tid, nullptr, routine, this);
    }

    void join()
    {
        pthread_join(_tid, nullptr);
    }
    //传入参数
    void entry()
    {
        _cb();
    }

    std::string name()
    {
        return _name;
    }

    pthread_t gettid()
    {
        return _tid;
    }

    uint64_t time()
    {
        return _time;
    }

    bool isrunning()
    {
        return _running;
    }

private:
    pthread_t _tid;
    std::string _name;
    uint64_t _time;
    bool _running;

    callback _cb;
};