【Linux】线程篇Ⅲ:线程池(代码案例)、读者写者模型

线程Ⅲ

  • 八. 线程池
  • 九. 读者写者模型
    • 1. 读写锁的一些接口


八. 线程池

池化技术本质就是空间换时间的技术,比如我们申请空间的时候,OS 会给我们多分配一些空间,在后续我们扩展空间的时候,直接线程的去访问这些空间。

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

线程池的应用场景:

  1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB 服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,比如一个热门网站的点击次数。 但对于长时间的任务,比如一个 Telnet 连接请求,线程池的优点就不明显了。因为 Telnet 会话时间比线程的创建时间大多了。
  2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。 突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。

线程池的种类:
线程池示例:

  1. 创建固定数量线程池,循环从任务队列中获取任务对象
  2. 获取到任务对象后,执行任务对象中的任务接口

代码实现:

Task.hpp:

#pragma once
#include 
#include 
#include 

class Task
{
public:
    Task()
    {}
    Task(int x, int y, char op) : _x(x), _y(y), _op(op), _result(0), _exitCode(0)
    {}
    void operator()()   // 仿函数
    {
        switch (_op)
        {
        case '+':
            _result = _x + _y;
            break;
        case '-':
            _result = _x - _y;
            break;
        case '*':
            _result = _x * _y;
            break;
        case '/':
        {
            if (_y == 0)
                _exitCode = -1;
            else
                _result = _x / _y;
        }
        break;
        case '%':
        {
            if (_y == 0)
                _exitCode = -2;
            else
                _result = _x % _y;
        }
        break;
        default:
            break;
        }

        usleep(100000);
    }
    std::string formatArg()
    {
        return std::to_string(_x) + _op + std::to_string(_y) + "= ?";
    }
    std::string formatRes()
    {
        return std::to_string(_result) + "(" + std::to_string(_exitCode) + ")";
    }
    ~Task()
    {}

private:
    int _x;	// 传入
    int _y;
    char _op;
    
    int _result;	// 传出
    int _exitCode;
};

Thread.hpp:

#pragma once
#include 
#include 
#include 
#include 

class Thread
{
public:
    typedef enum
    {
        NEW = 0,
        RUNNING,
        EXITED
    } ThreadStatus;
    typedef void (*func_t)(void *);

public:
    Thread(int num, func_t func, void *args) : _tid(0), _status(NEW), _func(func), _args(args)
    {
        char name[128];
        snprintf(name, sizeof(name), "thread-%d", num);
        _name = name;
    }
    int status() { return _status; }
    std::string threadname() { return _name; }
    pthread_t threadid()
    {
        if (_status == RUNNING)
            return _tid;
        else
        {
            return 0;
        }
    }
	
	// 注意 static 函数的特性
    static void *runHelper(void *args)
    {
        Thread *ts = (Thread*)args; // 就拿到了当前对象
        // _func(_args);
        (*ts)();
        return nullptr;
    }
    void operator ()() //仿函数
    {
        if(_func != nullptr) _func(_args);
    }
    void run()
    {
        int n = pthread_create(&_tid, nullptr, runHelper, this);
        if(n != 0) exit(1);
        _status = RUNNING;
    }
    void join()
    {
        int n = pthread_join(_tid, nullptr);
        if( n != 0)
        {
            std::cerr << "main thread join thread " << _name << " error" << std::endl;
            return;
        }
        _status = EXITED;
    }
    ~Thread()
    {}

private:
    pthread_t _tid;
    std::string _name;
    func_t _func; // 线程未来要执行的回调
    void *_args;
    ThreadStatus _status;
};

lockGuard.hpp:

#pragma once
#include 
#include 

class Mutex
{
public:
    Mutex(pthread_mutex_t *mutex):_pmutex(mutex)
    {}
    void lock()
    {
        pthread_mutex_lock(_pmutex);
    }
    void unlock()
    {
        pthread_mutex_unlock(_pmutex);
    }
    ~Mutex()
    {}
private:
    pthread_mutex_t *_pmutex;
};

class LockGuard // 锁由外部传入
{
public:
    LockGuard(pthread_mutex_t *mutex):_mutex(mutex)
    {
        _mutex.lock();
    }
    ~LockGuard()
    {
        _mutex.unlock();
    }
private:
    Mutex _mutex;
};

ThreadPool.hpp: (单例模式

#pragma once
#include 
#include 
#include 
#include 
#include 
#include "Thread.hpp"
#include "Task.hpp"
#include "lockGuard.hpp"

const static int N = 5;

template <class T>
class ThreadPool
{
private:
    ThreadPool(int num = N) : _num(num)
    {
        pthread_mutex_init(&_lock, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }
    ThreadPool(const ThreadPool<T> &tp) = delete;
    void operator=(const ThreadPool<T> &tp) = delete;

public:
    static ThreadPool<T> *getinstance()
    {
        if(nullptr == instance) // 为什么要这样?提高效率,减少加锁的次数!
        {
            LockGuard lockguard(&instance_lock);
            if (nullptr == instance)
            {
                instance = new ThreadPool<T>();
                instance->init();
                instance->start();
            }
        }
        return instance;
    }

    pthread_mutex_t *getlock()
    {
        return &_lock;
    }
    void threadWait()
    {
        pthread_cond_wait(&_cond, &_lock);
    }
    void threadWakeup()
    {
        pthread_cond_signal(&_cond);
    }
    bool isEmpty()
    {
        return _tasks.empty();
    }
    T popTask()
    {
        T t = _tasks.front();
        _tasks.pop();
        return t;
    }
    static void threadRoutine(void *args)
    {
        // pthread_detach(pthread_self());
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        while (true)
        {
            // 1. 检测有没有任务
            // 2. 有:处理
            // 3. 无:等待
            // 细节:必定加锁
            T t;
            {
                LockGuard lockguard(tp->getlock());
                while (tp->isEmpty())
                {
                    tp->threadWait();
                }
                t = tp->popTask(); // 从公共区域拿到私有区域
            }
            // for test
            t();
            std::cout << "thread handler done, result: " << t.formatRes() << std::endl;
            // t.run(); // 处理任务,应不应该在临界区中处理?1,0
        }
    }
    void init()
    {
        for (int i = 0; i < _num; i++)
        {
            _threads.push_back(Thread(i, threadRoutine, this));
        }
    }
    void start()
    {
        for (auto &t : _threads)
        {
            t.run();
        }
    }
    void check()
    {
        for (auto &t : _threads)
        {
            std::cout << t.threadname() << " running..." << std::endl;
        }
    }
    void pushTask(const T &t)
    {
        LockGuard lockgrard(&_lock);
        _tasks.push(t);
        threadWakeup();
    }
    ~ThreadPool()
    {
        for (auto &t : _threads)
        {
            t.join();
        }
        pthread_mutex_destroy(&_lock);
        pthread_cond_destroy(&_cond);
    }

private:
    std::vector<Thread> _threads;
    int _num;

    std::queue<T> _tasks; // 使用stl的自动扩容的特性

    pthread_mutex_t _lock;
    pthread_cond_t _cond;

    static ThreadPool<T> *instance;
    static pthread_mutex_t instance_lock;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::instance = nullptr;

template <class T>
pthread_mutex_t ThreadPool<T>::instance_lock = PTHREAD_MUTEX_INITIALIZER;

main.cc

#include "ThreadPool.hpp"
#include "Task.hpp"
#include 

int main()
{
    // 消费者:相当于把消费者包装在了类内部,充当了线程池
    // std::unique_ptr> tp(new ThreadPool(20));  // 智能指针 
    // tp->init();
    // tp->start();
    // tp->check(); 
    printf("0X%x\n", ThreadPool<Task>::getinstance());
    printf("0X%x\n", ThreadPool<Task>::getinstance());
    printf("0X%x\n", ThreadPool<Task>::getinstance());
    printf("0X%x\n", ThreadPool<Task>::getinstance());
    printf("0X%x\n", ThreadPool<Task>::getinstance());
    printf("0X%x\n", ThreadPool<Task>::getinstance());

    // 生产者
    while (true)
    {
        int x, y;
        char op;
        std::cout << "please Enter x> ";
        std::cin >> x;
        std::cout << "please Enter y> ";
        std::cin >> y;
        std::cout << "please Enter op(+-*/%)> ";
        std::cin >> op;

        Task t(x, y, op);
        ThreadPool<Task>::getinstance()->pushTask(t); //单例对象也有可能在多线程场景中使用!
    }
}

九. 读者写者模型

消费者会读走数据,读者不会对数据产生影响。这是读写和生产消费的区别!!

三种关系:

  • 读者 & 读者 -> 没有关系
  • 写者 & 写者 -> 互斥关系
  • 读者 & 写者 -> 同步 + 互斥关系

两种角色:

  • 读者和写者

一个交易场所:

  • 缓冲区(通常是)

注意: 写独占、读共享、读锁优先级更高

1. 读写锁的一些接口

  • 读锁(读者加锁)pthread_rwlock_rdlock

    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
    
  • 写锁(写者加锁)pthread_rwlock_wrlock

    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
    
  • 读写解锁 / 释放锁 pthread_rwlock_unlock

    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
    
  • 读写锁初始化 pthread_rwlock_init

    int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); 
    
  • 读写锁的销毁 pthread_rwlock_destroy

    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
    

伪代码如下:

int reader_cnt = 0;	// 记录读者在读的数量
pthread_mutex_t lock;
sem_t w(1);	// 写者信号量初始值设为 1

读者:

// 读
pthread_mutex_lock(&lock);
if(reader_cnt == 0) P(w);	// 如果读者发现此时没有人在读,就拿走写者的信号量,写者就没法写了
reader_cnt++;
读数据
pthread_mutex_un lock(&lock);

// 不读了
pthread_mutex_lock(&lock);
reader_cnt--;
if(reader_cnt == 0) V(w);	// 如果没人读了,把写者的信号量还回去
pthread_mutex_un lock(&lock);

写者:

P(w);
if(reader_cnt > 0) // 如果有人读,直接让出信号量
{
	V(w);	
	挂起等待;
}

// 申请到 P 锁了,即没有读者在读了
写入数据
V(w);

读者倘若很多一直不断的读取,导致写者写入不了数据,即写者饥饿,怎么办呢?

  1. 读者优先策略,上述代码就是,我们一般都是使用的这个策略。饥饿问题是正常的。

  2. 写者优先策略

你可能感兴趣的:(Linux,linux,运维,服务器)