Linux 多线程(线程池/线程安全的单例模式/STL容器线程安全问题/智能指针线程安全问题)

目录

线程池

线程安全的单例模式

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

STL线程安全的问题

智能指针线程安全问题


线程池

与之前的生产者消费者模型,  读者写者模型一样, 线程池也是为了解决某些典型场景下的问题而出现的.

举个例子, 当我们双十一疯狂剁手的时候, 假设我们用户的每一笔订单请求在服务器端都要创建一个线程的话, 当11.11号0点

来临, 每一年我们都在刷新着记录, 所以在短时间内会有数量非常非常大的各种任务请求, 如果服务器端要在这些请求到来时

在一个一个的创建线程的话, 需要非常大的时空开销. 其实, 这些线程也不一定要等待大量任务请来的时候才一个个创建, 我

们可以提前创建好解决各种任务的线程, 把他们放在"线程池"中, 当有任务请求来时, 直接将请求给"线程池", "线程池"中的线程

就可以直接完成这个任务请求. 免去了请求高峰时段频繁创建线程而带来的花销.

解决的问题 : 短时间大量任务请求下创建大量线程带来的时空开销, 调度开销, 以及有资源耗尽的风险

优点 : 避免峰值压力下, 资源耗尽的风险.  节省线程创建销毁的时间成本

实现 : 一堆线程(有最大数量限制) + 一个线程安全的任务队列

具体思路 :

思路简单来说, 就是一个ThreadPool的对象中创建许多个线程, 当这个对象的任务等待队列中有任务请求, 也就是push操作时, 唤醒所创建的线程去完成这个任务, 完成任务后, 这个任务就可以pop出任务等待队列. 那么请求任务也不一定是一样的啊, 就比如淘宝来说, 有买的请求, 也有退货的等等, 所以, 我们还需将任务处理的方法与数据封装为一个类ThreadTask, 在线程入口函数中, 只需要调用我们封装的这个类的接口, 不需要关心方法与数据是什么, 保证了我们线程库的实现中, 线程不需要关心要处理什么具体问题, 只需要调用一个固定的接口就可以. 大大降低了耦合性, 我们有不同的任务请求时, 只需要实例化出不同的ThreadTask对象就可以了.

我们来封装一个简单的线程池

ThreadPool.hpp

#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__
#include<iostream>
#include<pthread.h>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
using namespace std;

#define MAX_COUNT 10 
typedef void (*task_handler_t)(int);
class ThreadTask{
    public:
    int m_data;
    task_handler_t m_handler;
public:
    ThreadTask(int data, task_handler_t handler) :
        m_data(data), m_handler(handler)
    {}
    void Run(){
        m_handler(m_data);
    } 
};
class ThreadPool{
    size_t m_max_count;//定义线程池中线程的数量
    queue<ThreadTask> m_task_q;//任务队列
    pthread_mutex_t m_mutex;//互斥锁
    pthread_cond_t m_pro_cond, m_con_cond;//条件变量
    bool m_exit_flag;//析构标记
    vector<pthread_t> m_tid_array;//线程池中所有线程的tid
    size_t m_destroy_count;//销毁线程计数

    static void* thr_start(void* arg);
    void Quit();
public:
    ThreadPool(size_t max_count = MAX_COUNT); 
    ~ThreadPool();
    bool PushTask(const ThreadTask& task);
};

ThreadPool::ThreadPool(size_t max_count) :
    m_max_count(max_count),
    m_exit_flag(false),
    m_destroy_count(0),
    m_tid_array(max_count) {
    pthread_mutex_init(&m_mutex, NULL);
    pthread_cond_init(&m_con_cond, NULL);
    pthread_cond_init(&m_pro_cond, NULL);
    int ret;
    for(size_t i = 0; i < m_max_count; ++i){
        ret = pthread_create(&(m_tid_array[i]), NULL, thr_start, this);
        if(ret){
            fprintf(stderr, "create thread:%s\n", strerror(ret));
            exit(0);
        }
        //若不关心线程返回值, 并希望线程退出后能够自己释放资源
        //pthread_detach(m_tid_array[i]);//则分离这个线程
        //否则需要记录所创建所有线程的tid, 再析构时对所有线程pthread_join
    }
}
ThreadPool::~ThreadPool(){
    Quit();
    for(size_t i = 0; i < m_max_count; pthread_join(m_tid_array[i], NULL), ++i);
    //detach属性则不需要等待
    pthread_mutex_destroy(&m_mutex);
    pthread_cond_destroy(&m_pro_cond);
    pthread_cond_destroy(&m_con_cond);
}
void* ThreadPool::thr_start(void* arg){
    ThreadPool* tp = (ThreadPool*)arg;
    while(1){
        pthread_mutex_lock(&(tp->m_mutex));
        while(tp->m_task_q.empty()){
            if(tp->m_exit_flag) {
                ++(tp->m_destroy_count);
                pthread_mutex_unlock(&(tp->m_mutex));
                cout<<"线程退出\n";//用于测试
                pthread_exit(NULL);           
            }
            pthread_cond_wait(&(tp->m_con_cond), &(tp->m_mutex));
        }
        ThreadTask task = tp->m_task_q.front();
        tp->m_task_q.pop();
        pthread_mutex_unlock(&(tp->m_mutex));

        task.Run();//要解锁之后再进行任务处理
        pthread_cond_signal(&(tp->m_pro_cond));
    }
    pthread_exit(NULL);
}
bool ThreadPool::PushTask(const ThreadTask& task){
    pthread_mutex_lock(&m_mutex);
    m_task_q.push(task);
    pthread_mutex_unlock(&m_mutex);

    pthread_cond_signal(&m_con_cond);
    return true;
}
void ThreadPool::Quit(){
    pthread_mutex_lock(&m_mutex);
    m_exit_flag = true;
    pthread_mutex_unlock(&m_mutex);
    while(m_destroy_count != m_max_count) {
        pthread_cond_broadcast(&m_con_cond);
    }
}
#endif

main.cpp

#include<iostream>
#include<cstdio>
#include<pthread.h>
#include"thread_pool.hpp"
void test(int data){
    printf("thread:%p get data:%d\n", pthread_self(), data);
}
int main(int argc, char* argv[]){
    if(argc != 2){
        return -1;
    }
    ThreadPool pool(4);
    for(int i = 0; i < atoi(argv[1]); ++i){
        pool.PushTask(ThreadTask(i, test));
    }
    return 0;
}


线程安全的单例模式

单例模式,属于创建类型的一种常用的设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)

设计模式 : 针对一些经典的常见的场景, 制定的一些对应的解决方案, 就是设计模式.

单例模式的特点

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

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

针对场景 : 在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据(也就是一份资源只加载一次). 

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

什么是饿汉和饱汉呢 ?  举个例子

吃完饭之后, 马上去洗碗, 下回吃饭之前就不需要洗完了, 这就是饿汉,  即在程序初始化阶段就去加载资源, 后面程序运行只需要使用.  优点 : 运行流畅   缺点 : 初始加载较慢

吃完饭之后, 先不去洗碗, 等到下回吃饭之前再洗, 这就是懒汉, 即程序只有在使用某些资源时才会去加载, 优点 : 初始加载快   缺点 : 运行时可能会会加载资源, 可能会不够流畅

懒汉模式的核心是"延时加载", 从而优化程序的启动速度

饿汉模式实现单例模式

using namespace std;
template<class T>
class singleton{
    static T m_data;
public:
    T* gey_instance(){
        return &m_data;
    }
};
template<class T>
T singleton<T>::m_data = 0;

懒汉模式实现单例模式

template<class T>
class singleton{
    volatile static T* m_data;
    static mutex m_mutex;
public:
    volatile T* get_instance(){
        if(m_data == nullptr){
            m_mutex.lock();
            if(m_data == nullptr){
                m_data = new int;
            }   
            m_mutex.unlock();
        }
        return m_data;
    }
    ~singleton(){
        if(m_data){
            delete m_data;
            m_data = nullptr;
        }
    }
};
template<class T>
volatile T* singleton<T>::m_data = nullptr;

需要注意的是, 懒汉模式存在线程安全的问题, 所以需要实现线程安全.

注意事项:

  • 1. 加锁解锁的位置
  • 2. 双重 if 判定, 避免不必要的锁竞争
  • 3. volatile关键字防止过度优化(保持内存可见性)

STL线程安全的问题

STL在设计之初, 就是为了极致的性能, 而锁对极致性能来说是无情的杀手, 所以就没有实现线程安全. 所以使用STL中的容器

等存在线程安全问题, 需要我们对具体问题, 具体设计, 比如上面的线程池中任务队列的push和pop就需要我们手动实现线程

安全.


智能指针线程安全问题

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

对于 shared_ptr, 多个对象需要共用一个引用计数变量(shared_ptr内部有引用计数), 所以会存在线程安全问题. 但是标准库

实现的时候考虑到了这个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数. 所以说智能指针

是线程安全的.

智能指针相关博客, 戳链接( ̄︶ ̄)↗ C++ 智能指针(auto_ptr/unique_ptr/shared_ptr)


相关博客, 戳链接( ̄︶ ̄)↗ :  Linux 多线程(线程概念/特点/优缺点/与进程比较)
                                                     Linux 多线程(线程控制(创建/终止/等待/分离))
                                                     Linux 多线程之线程安全(同步与互斥/互斥锁/条件变量/死锁/)
                                                     Linux 多线程之线程安全(生产者消费者模型/POSIX信号量/读者写者模型/读写锁/自旋锁)

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值