线程(四)线程池的实现+线程的单例模式

时间:2022-07-24
本文章向大家介绍线程(四)线程池的实现+线程的单例模式,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

线程池的实现

什么是线程池

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

线程池的应用场景

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

线程池示例:

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

线程池的实现

#ifndef __M_TP_H__
#define __M_TP_H__
#include <iostream>
#include <queue>
#include <pthread.h>
 
#define MAX_THREAD  5
typedef bool (*handler_t)(int);
class ThreadTask
{
    private:
        int _data;
        handler_t _handler;
    public:
        ThreadTask():_data(-1), _handler(NULL) {}
        ThreadTask(int data, handler_t handler) {
            _data= data;
            _handler = handler;
        }   
        void SetTask(int data, handler_t handler) {
            _data = data;
            _handler = handler;
        }   
        void Run() {
            _handler(_data);
        }   
};
 
class ThreadPool
{
    private:
        int _thread_max;
        int _thread_cur;
        bool _tp_quit;
        std::queue<ThreadTask *> _task_queue;
        pthread_mutex_t _lock;
        pthread_cond_t _cond;
    private:
        void LockQueue() {
            pthread_mutex_lock(&_lock);
        }
        void UnLockQueue() {
            pthread_mutex_unlock(&_lock);
        }
        void WakeUpOne() {
            pthread_cond_signal(&_cond);
        }
        void WakeUpAll() {
            pthread_cond_broadcast(&_cond);
        }
        void ThreadQuit() {
            _thread_cur--;
            UnLockQueue();
            pthread_exit(NULL);
        }
        void ThreadWait(){
            if (_tp_quit) {
                ThreadQuit();
            }
            pthread_cond_wait(&_cond, &_lock);
        }
        bool IsEmpty() {
            return _task_queue.empty();
        }
        static void *thr_start(void *arg) {
            ThreadPool *tp = (ThreadPool*)arg;
            while(1) {
                tp->LockQueue();
                while(tp->IsEmpty()) {
                    tp->ThreadWait();
                }
                ThreadTask *tt;
                tp->PopTask(&tt);
                tp->UnLockQueue();
                tt->Run();
                delete tt;
            }
            return NULL;
        }
    public:
        ThreadPool(int max=MAX_THREAD):_thread_max(max), _thread_cur(max), 
        _tp_quit(false) {
            pthread_mutex_init(&_lock, NULL);
            pthread_cond_init(&_cond, NULL);
        }
        ~ThreadPool() {
            pthread_mutex_destroy(&_lock);
            pthread_cond_destroy(&_cond);
        }
        bool PoolInit() {
            pthread_t tid;
            for (int i = 0; i < _thread_max; i++) {
                int ret = pthread_create(&tid, NULL, thr_start, this);
                if (ret != 0) {
                    std::cout<<"create pool thread errorn";
                    return false;
                }
            }
            return true;
        }
        bool PushTask(ThreadTask *tt) {
            LockQueue();
            if (_tp_quit) {
                UnLockQueue();
                return false;
            }
            _task_queue.push(tt);
            WakeUpOne();
            UnLockQueue();
            return true;
        }
        bool PopTask(ThreadTask **tt) {
            *tt = _task_queue.front();
            _task_queue.pop();
            return true;
        }
        bool PoolQuit() {
            LockQueue();
            _tp_quit = true;
            UnLockQueue();
            while(_thread_cur > 0) {
                WakeUpAll();
                usleep(1000);
            }
            return true;
        }
};
#endif
 
 
bool handler(int data)
{   
    srand(time(NULL));
    int n = rand() % 5;
    printf("Thread: %p Run Tast: %d--sleep %d secn", pthread_self(), data, n);
    sleep(n);
    return true;
}
int main()
{
    int i;
 
    ThreadPool pool;
    pool.PoolInit();
    for (i = 0; i < 10; i++) {
        ThreadTask *tt = new ThreadTask(i, handler);
        pool.PushTask(tt);
    }
 
    pool.PoolQuit();
    return 0;
}

运行结果:

线程安全的单例模式

什么是单例模式

单例模式是一种 “经典的, 常用的, 常考的” 设计模式.

单例模式的特点

  • 某些类, 只应该具有一个对象(实例), 就称之为单例.
  • 在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据.

饿汉实现方式和懒汉实现方式

懒汉方式最核心的思想是 “延时加载”. 从而能够优化服务器的启动速度.

饿汉模式实现单例模式
template <typename T>
class Singleton {
  static T data;
public:
  static T* GetInstance() {
    return &data;
  }
};
懒汉模式实现单例模式
template <typename T>
class Singleton {
  static T* inst;
public:
  static T* GetInstance() {
    if (inst == NULL) {
      inst = new T();
    }
    return inst;
  }
};

懒汉方式实现单例模式(线程安全版本)

// 懒汉模式, 线程安全
template <typename T>
class Singleton {
  volatile static T* inst;  // 需要设置 volatile 关键字, 否则可能被编译器优化.
  static std::mutex lock;
public:
  static T* GetInstance() {
    if (inst == NULL) {     // 双重判定空指针, 降低锁冲突的概率, 提高性能.
      lock.lock();          // 使用互斥锁, 保证多线程情况下也只调用一次 new.
      if (inst == NULL) {
        inst = new T();
      }
      lock.unlock();
    }
    return inst;
  }
};

注意事项:

  1. 加锁解锁的位置
  2. 双重 if 判定, 避免不必要的锁竞争
  3. volatile关键字防止过度优化