Linux——线程7|线程池

线程池

线程池以空间换时间,预先申请一批线程,当有任务到来,直接指派线程。

由于这是类的成员函数,因此传参的时候会有一个this指针,我们如果想在传参的时候不传这个this指针,我们只需加上static。

这里只传一个参数,是因为我们的routine是这种类型的

线程池本质也是一个生产消费模型。

同样在消费过程中,我们要是直接从任务队列里弹出,程序会报错

这是因为task_queue是类内方法,要在静态方法里使用,必须定义成静态方法。现在无法直接使用类内的一些属性,无法读取任务队列。

我们在构造方法这里传入this指针,

之后通过this指针调用各种接口

这里等待的时候,是在临界区中进行等待,因此需要释放锁,当被唤醒得时候再重新持有锁

写日志时用到的可变参数

Task.hpp

#ifndef _TEST_H_
#define _TEST_H_
#include<functional>
#include<iostream>
#include<string>
#include"log.hpp"
typedef std::function<int(int,int)> func_t;
class Task
{
public:
     Task() {}
     Task(int x,int y,func_t func)
     :x_(x),y_(y),func_(func)
     {}
     void operator()(const std::string& name)
     {
     logMessage(WARNING,"%s处理完成: %d+%d=%d | %s | %d",name.c_str(),x_,y_,func_(x_,y_),__FILE__,__LINE__);
     }
 public:
     int x_;
    int y_;
    func_t func_;
};
#endif

lockGuard.hpp

#pragma once

#include <iostream>
#include <pthread.h>

class Mutex
{
public:
    Mutex(pthread_mutex_t *mtx):pmtx_(mtx)
    {}
    void lock() 
    {
        // std::cout << "要进行加锁" << std::endl;
        pthread_mutex_lock(pmtx_);
    }
    void unlock()
    {
        // std::cout << "要进行解锁" << std::endl;
        pthread_mutex_unlock(pmtx_);
    }
    ~Mutex()
    {}
private:
    pthread_mutex_t *pmtx_;
};

// RAII风格的加锁方式
class lockGuard
{
public:
    lockGuard(pthread_mutex_t *mtx):mtx_(mtx)
    {
        mtx_.lock();
    }
    ~lockGuard()
    {
        mtx_.unlock();
    }
private:
    Mutex mtx_;
};

log.hpp

#pragma once

#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <ctime>
#include <string>

// 日志是有日志级别的
#define DEBUG   0
#define NORMAL  1
#define WARNING 2
#define ERROR   3
#define FATAL   4

const char *gLevelMap[] = {
    "DEBUG",
    "NORMAL",
    "WARNING",
    "ERROR",
    "FATAL"
};

#define LOGFILE "./threadpool.log"

// 完整的日志功能,至少: 日志等级 时间 支持用户自定义(日志内容, 文件行,文件名)
void logMessage(int level, const char *format, ...)
{
#ifndef DEBUG_SHOW
    if(level== DEBUG) return;
#endif
    // va_list ap;
    // va_start(ap, format);
    // while()
    // int x = va_arg(ap, int);
    // va_end(ap); //ap=nullptr
    char stdBuffer[1024]; //标准部分
    time_t timestamp = time(nullptr);
    // struct tm *localtime = localtime(&timestamp);
    snprintf(stdBuffer, sizeof stdBuffer, "[%s] [%ld] ", gLevelMap[level], timestamp);

    char logBuffer[1024]; //自定义部分
    va_list args;
    va_start(args, format);
    // vprintf(format, args);
    vsnprintf(logBuffer, sizeof logBuffer, format, args);
    va_end(args);

    FILE *fp = fopen(LOGFILE, "a");
    // printf("%s%s\n", stdBuffer, logBuffer);
    fprintf(fp, "%s%s\n", stdBuffer, logBuffer);
    fclose(fp);
}

makefile

thread_pool:testMain.cc
    g++ -o $@ $^ -std=c++11 -lpthread -DDEBUG_SHOW
.PHONY:clean
clean:
    rm -rf thread_pool

testMain.cc

#include"threadPool.hpp"
#include"Task.hpp"
#include"log.hpp"
#include<ctime>
#include<cstdlib>
#include<unistd.h>
int main()
{
srand((unsigned long)time(nullptr)^getpid());
// std::cout<<"hello thread pool"<<std::endl;
ThreadPool<Task> *tp=new ThreadPool<Task>();//构造一个线程池
tp->run();//启动线程
while(true)
{
 //生产得过程,制作任务得时候,是要花时间的
 int x=rand()%100+1;
 usleep(7721);
 int y=rand()%30+1;
 Task t(x,y,[](int x,int y)->int{return x+y;});
 
        logMessage(DEBUG, "制作任务完成: %d+%d=?", x, y);
        logMessage(DEBUG, "制作任务完成: %d+%d=?", x, y);
        logMessage(DEBUG, "制作任务完成: %d+%d=?", x, y);
        logMessage(DEBUG, "制作任务完成: %d+%d=?", x, y);

 //推送任务到线程池中
 tp->pushTask(t);
        sleep(1);
}
return 0;
}

thread.hpp

#pragma once
#include<iostream>
#include<string>
#include<functional>
#include<cstdio>
//typedef std::function<void*(void*)> fun_t;
typedef void*(*fun_t)(void*);
class ThreadData
{
 public:
     void *args_;
    std::string name_;
};
class Thread
{
public:
    Thread(int num,fun_t callback,void *args)//num代表线程的编号
    :func_(callback)
    {
     char nameBuffer[64];
     snprintf(nameBuffer,sizeof nameBuffer,"Thread-%d",num);//把线程名写到buffer中
     name_=nameBuffer;//线程名字等于 nameBuffer
     tdata_.args_=args;
     tdata_.name_=name_;
    }
    /*void *routine(void *args)
    {
    }*/
    void start()//启动线程函数
    {
     pthread_create(&tid_,nullptr,func_,(void*)&tdata_);
    }
    void join()
    {
     pthread_join(tid_,nullptr);
    }
    std::string name()
    {
     return name_;
    }
    ~Thread()
    {}
 private:
     std::string name_;//线程名
    fun_t func_;
    ThreadData tdata_;
    pthread_t tid_;//线程id
};

threadPool.hpp

#pragma once
#include<iostream>
#include<vector>
#include<unistd.h>
#include<queue>
#include"thread.hpp"
#include"log.hpp"
#include<string>
#include"Task.hpp"
#include"lockGuard.hpp"
const int g_thread_num=3;//默认线程数
template <class T>
class ThreadPool
{
public:
    pthread_mutex_t *getMutex()
    {
     return &lock;//返回锁的地址
    }
    bool isEmpty()//判断任务队列是否为空
    {
      return task_queue_.empty();
    }
    void waitCond()//在条件变量下进行等待
    {
    pthread_cond_wait(&cond,&lock);//在特定的条件变量下去等待,并且释放我们的锁 ,这个接口在返回时会自动持有锁
    }
    T getTask()
    {
     T t=task_queue_.front();
     task_queue_.pop();
     return t;
    }
public:
    ThreadPool(int thread_num=g_thread_num)
    :num_(thread_num)
    {
     pthread_mutex_init(&lock,nullptr);
     pthread_cond_init(&cond,nullptr);//条件变量
     for(int i=0;i<num_;i++)
     {
      threads_.push_back(new Thread(i,routine,this));//创建线程对象
     }
    }
    void run()
    {
     for(auto &iter:threads_)
     {
      iter->start();
      logMessage(NORMAL,"%s %s",iter->name().c_str(),"启动成功");
     }
    }
    void pushTask(const T& Task)
    {
     //给生产者加锁
     lockGuard lockguard(&lock);
     task_queue_.push(Task);//把任务放入任务队列
     //生产完任务后,还要让指定的线程去运行任务
     pthread_cond_signal(&cond);//唤醒线程
    }
    //消费过程
    static void *routine(void* args)
    {
    ThreadData *td=(ThreadData*)args;
    ThreadPool<T> *tp=(ThreadPool<T>*)td->args_;//args_就是对应的this指针
     while(true)
     {
      T task;//任务对象
      { 
        lockGuard lockguard(tp->getMutex());//加锁
        while(tp->isEmpty())//如果任务队列为空,就等待
             tp->waitCond();//该函数在等待的时候,会自动释放锁,
         //读取任务
         task=tp->getTask();//将任务从共享,拿到自己得私有空间
      }
      task(td->name_);
     }
    }
    void show()
    {
     std::cout<<"可以传入this,让使用静态方法的线程,访问到线程池内对应的方法"<<std::endl;
    }
/*    void joins()//临时接口,用来做测试
    {
     for(auto &iter:threads_)
     {
      iter->join();//回收线程
     } 
    }
    */
    ~ThreadPool()
    {
     for(auto &iter:threads_)
     {
      iter->join();//回收线程
      delete iter;//把所有的线程对象delete掉
     }
     pthread_mutex_destroy(&lock);
     pthread_cond_destroy(&cond);
    }
private:
    std::vector<Thread*> threads_;
    int num_;
    std::queue<T> task_queue_;//任务队列,生产出来的任务放到这里
    pthread_mutex_t lock;
    pthread_cond_t cond;//加一个条件变量
};

单例模式

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

什么是设计模式?

IT行业这么火, 涌入的人很多. 俗话说林子大了啥鸟都有. 大佬和菜鸡们两极分化的越来越严重. 为了让菜鸡们不太拖大

佬的后腿, 于是大佬们针对一些经典的常见的场景, 给定了一些对应的解决方案, 这个就是 设计模式

单例模式的特点

某些类, 只应该具有一个对象(实例), 就称之为单例.

例如一个男人只能有一个媳妇.

在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这

些数据.

我们把上述的线程池改为单例模式

这里定义了一个静态的指针,在类外对静态成员初始化

单例模式下的构造函数是私有的

该方法是类的成员方法,默认情况下无法访问类内的静态成员,所以要加static

线程创建成功

如果单例本身也被多线程使用呢?我们就需要考虑一下多线程使用单例的过程。

上面的代码可能会遇到这种情况,多个线程同时调用getThreadPool,当第一个线程用if语句判断时,可能被切走,被且走后第二个线程进来,通过了if判断,然后new出对象,此时第一个线程又被切回来,但这个时候已经在执行new代码了,影刺可能会出现new重复执行的情况,所以我们上面的代码并不是线程安全的。

因此我们要在这部分加锁

任何一个线程想获取单例,都必须调用getThreadPool接口,但这个接口里面的if语句,在第一次判断时是有意义的,后面的判断无任何意义(thread_ptr指针在第一次创建好对象之后,往后就不会位空),若有大量的多线程获取单例,这里就会存在大量申请和释放锁的行为,这个是无用且浪费资源的。

代码这样改,当再想获取单列的时候,先进行判断而不是加锁,当后续的线程来的时候ptr已经不为空了,直接进行返回,避免了中间的多次加解锁。

threadPool.hpp

#pragma once
#include<iostream>
#include<vector>
#include<unistd.h>
#include<queue>
#include"thread.hpp"
#include"log.hpp"
#include<string>
#include<pthread.h>
#include"Task.hpp"
#include"lockGuard.hpp"
const int g_thread_num=3;//默认线程数
template <class T>
class ThreadPool
{
public:
    pthread_mutex_t *getMutex()
    {
     return &lock;//返回锁的地址
    }
    bool isEmpty()//判断任务队列是否为空
    {
      return task_queue_.empty();
    }
    void waitCond()//在条件变量下进行等待
    {
    pthread_cond_wait(&cond,&lock);//在特定的条件变量下去等待,并且释放我们的锁 ,这个接口在返回时会自动持有锁
    }
    T getTask()
    {
     T t=task_queue_.front();
     task_queue_.pop();
     return t;
    }
private:
    ThreadPool(int thread_num=g_thread_num)
    :num_(thread_num)
    {
     pthread_mutex_init(&lock,nullptr);
     pthread_cond_init(&cond,nullptr);//条件变量
     for(int i=0;i<num_;i++)
     {
      threads_.push_back(new Thread(i,routine,this));//创建线程对象
     }
    }
    ThreadPool(const ThreadPool<T>& other)=delete;
    const ThreadPool<T> operator=(const ThreadPool<T>& other)=delete;
public:
    static ThreadPool<T>* getThreadPool()//这个函数是可以带参数的
    {
     if(nullptr==thread_ptr)//可以有效减少未来必定要进行加锁检测的问题
    {
     lockGuard lockguard(&mutex);
     if(thread_ptr==nullptr)
     {
      thread_ptr=new ThreadPool<T>();
     }
        }
     return thread_ptr;
      }
    void run()
    {
     for(auto &iter:threads_)
     {
      iter->start();
      logMessage(NORMAL,"%s %s",iter->name().c_str(),"启动成功");
     }
    }
    void pushTask(const T& Task)
    {
     //给生产者加锁
     lockGuard lockguard(&lock);
     task_queue_.push(Task);//把任务放入任务队列
     //生产完任务后,还要让指定的线程去运行任务
     pthread_cond_signal(&cond);//唤醒线程
    }
    //消费过程
    static void *routine(void* args)
    {
    ThreadData *td=(ThreadData*)args;
    ThreadPool<T> *tp=(ThreadPool<T>*)td->args_;//args_就是对应的this指针
     while(true)
     {
      T task;//任务对象
      { 
        lockGuard lockguard(tp->getMutex());//加锁
        while(tp->isEmpty())//如果任务队列为空,就等待
             tp->waitCond();//该函数在等待的时候,会自动释放锁,
         //读取任务
         task=tp->getTask();//将任务从共享,拿到自己得私有空间
      }
      task(td->name_);
     }
    }
    void show()
    {
     std::cout<<"可以传入this,让使用静态方法的线程,访问到线程池内对应的方法"<<std::endl;
    }
/*    void joins()//临时接口,用来做测试
    {
     for(auto &iter:threads_)
     {
      iter->join();//回收线程
     } 
    }
    */
    ~ThreadPool()
    {
     for(auto &iter:threads_)
     {
      iter->join();//回收线程
      delete iter;//把所有的线程对象delete掉
     }
     pthread_mutex_destroy(&lock);
     pthread_cond_destroy(&cond);
    }
private:
    std::vector<Thread*> threads_;
    static pthread_mutex_t mutex;
    int num_;
    std::queue<T> task_queue_;//任务队列,生产出来的任务放到这里
    static ThreadPool<T>* thread_ptr;//单例模式,这是一个线程池的指针
    pthread_mutex_t lock;
    pthread_cond_t cond;//加一个条件变量
};
//单例模式
template<class T>
ThreadPool<T>* ThreadPool<T>::thread_ptr=nullptr;
template<class T>
pthread_mutex_t ThreadPool<T>::mutex=PTHREAD_MUTEX_INITIALIZER;

STL,智能指针和线程安全

STL中的容器是否是线程安全的?

不是.

原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响.

而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶).

因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全.

智能指针是否是线程安全的?

对于 unique_ptr, 由于只是在当前代码块范围内生效, 因此不涉及线程安全问题.

对于 shared_ptr, 多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这

个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数.

其他常见的各种锁

悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行

锁等),当其他线程想要访问数据时,被阻塞挂起。

乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,

会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。

CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不

等则失败,失败则重试,一般是一个自旋的过程,即不断重试。

自旋锁,公平锁,非公平锁?

自旋锁

自旋锁:本质就是通过不断检测锁状态,来进行资源是否就绪的方案。

什么时候使用自旋锁呢?一般临界资源就绪时间短的情况下使用自旋锁。

读者写者问题

在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的

机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地

降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。

读者,写者问题和生产消费的本质区别

消费者会取走数据,读者不会。读者和读者之间是共享关系,消费者和消费者之间互斥

reader_count是统计读者个数的

当没有读者的时候,写者才进行写入行为

读写锁接口

读方式加锁

写方式加锁

读者和写者谁优先?

应用场景一般在数据被读取的频率非常高,而被修改的频率特别低。一般来说,读者优先。读者,写者同时到来,先让读者执行任务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

头发没有代码多

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值