线程池|单例模式|STL、智能指针线程安全|读者写者问题

news2024/11/23 8:49:14

线程池

线程池的逻辑思想:

每当我们处理一个任务就要创建一个线程,创建线程的开销是很大的。因此我们可以预先创建一批线程,任务队列里没有任务的时候,每个线程都休眠,当队里中有任务的时候,就可以唤醒线程进行处理。唤醒线程的成本比创建整个线程的成本小,这就是线程池的逻辑思想。

线程池的概念:

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

线程池常见的应用场景:

需要大量的线程来完成任务,且完成任务的时间比较短。

对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。

接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。

代码实现

首先我们先熟悉一下整体的框架:

Thread.hpp

#pragma once
#include <pthread.h>
#include <iostream>
#include <cassert>
#include <string>
#include <functional>
namespace ThreadNs
{
    typedef std::function<void*(void*)> func_t;
    const int num  =1024;
    class Thread
    {
    private:
        static void* start_routine(void*args)
        {
            Thread* _this = static_cast<Thread*>(args);
            return _this->callback();
        }
    public:
        Thread()
        {
            char namebuffer[num];
            snprintf(namebuffer,sizeof namebuffer,"thread-%d",threadnum++);
            name_ = namebuffer;
        }

        void start(func_t func,void*args = nullptr)
        {
            func_ = func;
            args_ = args;
            int n = pthread_create(&tid_,nullptr,start_routine,this);
            assert(n==0);
            (void)n;
        }

        void join()
        {
            int n = pthread_join(tid_,nullptr);
            assert(n==0);
            (void)n;
        }

        std::string threadname()
        {
            return name_;
        }

        ~Thread()
        {}

        void* callback()
        {
            return func_(args_);
        }
    private:
        std::string name_;
        func_t func_;
        void *args_;
        pthread_t tid_;
        static int threadnum;
    };
    int Thread::threadnum = 1;
}

 LockGuard.hpp

#include <iostream>
#include <mutex>
class Mutex
{
public:
    Mutex(pthread_mutex_t*lock_p=nullptr)
    :lock_p_(lock_p)
    {}
    void lock()
    {
        if(lock_p_) pthread_mutex_lock(lock_p_);
    }
    void unlock()
    {
        if(lock_p_) pthread_mutex_unlock(lock_p_);
    }
    ~Mutex()
    {}
private:
    pthread_mutex_t * lock_p_;
};
class LockGuard
{
public:
    LockGuard(pthread_mutex_t*mutex)
    :mutex_(mutex)
    {
        mutex_.lock();
    }

    ~LockGuard()
    {
        mutex_.unlock();
    }
private:
    Mutex mutex_;
};

ThreadPool.hpp

#pragma once
#include "Thread.hpp"
#include "LockGuard.hpp"
#include <vector>
#include <queue>
#include <pthread.h>
#include <mutex>
#include <unistd.h>
using namespace ThreadNs;
const int gnum = 3;
template <class T>
class ThreadPool;

template <class T>
class ThreadData
{
public:
    ThreadPool<T> *threadpool;
    std::string name;
public:
    ThreadData(ThreadPool<T> *tp, const std::string &n) : threadpool(tp), name(n)
    { }
};
template <class T>
class ThreadPool
{
private:
    static void *handlerTask(void *args)
    {
        ThreadData<T> *td = (ThreadData<T> *)args;
        ThreadPool<T> *threadpool = static_cast<ThreadPool<T> *>(args);
        while (true)
        {
            T t;
            {
                LockGuard lockguard(td->threadpool->mutex());
                while(td->threadpool->isQueueEmpty())
                {
                    td->threadpool->threadWait();
                }
                t = td->threadpool->pop(); 
            }
            std::cout << td->name << " 获取了一个任务" << t.toTaskString() << "并处理完成,结果是: " << t() << std::endl;
        }
        delete td;
        return nullptr;
    }
public:
    void lockQueue() { pthread_mutex_lock(&_mutex); }
    void unlockQueue() { pthread_mutex_unlock(&_mutex); }
    bool isQueueEmpty() { return _task_queue.empty(); }
    void threadWait() { pthread_cond_wait(&_cond, &_mutex); }
    T pop()
    {
        T t = _task_queue.front();
        _task_queue.pop();
        return t;
    }
    
    void Push(const T &in)
    {
        LockGuard lockguard(&_mutex);
        _task_queue.push(in);
        pthread_cond_signal(&_cond);
    }

    pthread_mutex_t *mutex()
    {
        return &_mutex;
    }

public:
    ThreadPool(const int &num = gnum) : _num(num)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
        for (int i = 0; i < _num; i++)
        {
            _threads.push_back(new Thread());
        }
    }

    void run()
    {
        for (const auto &t : _threads)
        {
            ThreadData<T> *td = new ThreadData<T>(this, t->threadname());
            t->start(handlerTask, td);
            std::cout << t->threadname() << "start..." << std::endl;
        }
    }

    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
        for (const auto &t : _threads)
            delete t;
    }
private:
    int _num;
    std::vector<Thread *> _threads;
    std::queue<T> _task_queue;
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;
};

我们将线程池进行了模板化,因此线程池当中存储的任务类型可以是任意的;现在我们想像之前处理各种数据的计算,那么先引入任务组件Task.hpp:

#pragma once
#include <iostream>
#include <functional>
class Task
{
    using func_t = std::function<int(int,int ,char)>;
public:
    Task(){}

    Task(int x,int y,char op,func_t func)
    :_x(x),_y(y),_op(op),_callback(func)
    {}

    std::string operator()()
    {
        int result = _callback(_x,_y,_op);
        char buffer[1024];
        snprintf(buffer,sizeof buffer,"%d %c %d = %d",_x,_op,_y,result);
        return buffer;
    }

    std::string toTaskString()
    {
        char buffer[1024];
        snprintf(buffer,sizeof buffer,"%d %c %d = ?",_x,_op,_y);
        return buffer;
    }
private:
    int _x;
    int _y;
    char _op;
    func_t _callback;
};

const std::string oper = "+-*/%";
int mymath(int x,int y,char op)
{
    int result = 0;
    switch(op)
    {
    case '+':
        result = x+y;
        break;
    case '-':
        result = x-y;
        break;
    case '*':
        result = x*y;
        break;
    case '/':
        if(y==0)
        {
            std::cerr<<"div zero error!"<<std::endl;
            result = -1;
        }
        else
        {
            result = x/y;
        }
        break;
    case '%':
        if(y==0)
        {
            std::cerr<<"mod zero error!"<<std::endl;
            result = -1;
        }
        else
        {
            result = x%y;
        }
        break;
    default:
        break;
    }
    return result;
}

main.cc

#include "ThreadPool.hpp"
#include "Thread.hpp"
#include "Task.hpp"
#include <unistd.h>
#include <ctime>
int main()
{
   ThreadPool<Task>* tp = new ThreadPool<Task>();
   tp->run();
   srand(time(0));
   int x,y;
   char op;
   while(true)
   {
    x = rand()%10+1;
    y = rand()%20+1;
    op  =oper[rand()%oper.size()];
    Task t(x,y,op,mymath);
    tp->Push(t);
    sleep(1);
   }
    return 0;
}

单例模式

单例模式是一种创建型设计模式,它保证一个类只有一个实例存在,并且提供一个全局访问点来访问该实例。单例模式有饿汉模式和懒汉模式。

洗完的例子:

吃完饭 , 立刻洗碗 , 这种就是 饿汉方式 . 因为下一顿吃的时候可以立刻拿着碗就能吃饭 .
吃完饭 , 先把碗放下 , 然后下一顿饭用到这个碗了再洗碗 , 就是 懒汉方式 .

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

饿汉方式实现单例模式

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;
}
};

这样的懒汉设计可能会导致线程不安全,第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例。因此以下才是安全版的懒汉单例模式:

// 懒汉模式 , 线程安全
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;
    }
};

 线程池的单例模式

我们要做的第一步就是把构造函数私有,再把拷贝构造和赋值运算符重载delete:

 接下来就要在成员变量中定义一个静态指针,方便获取单例对象:

在设置获取单例对象的函数的时候,注意要设置成静态成员函数,因为在获取对象前根本没有对象,无法调用非静态成员函数(无this指针):

主函数的调用:

不过也许会出现多个线程同时申请资源的场景,所以还需要一把锁来保护这块资源,而这把锁也得设置成静态,因为GetSingle()函数是静态的:

 STL,智能指针和线程安全

STL 的设计初衷是将性能挖掘到极致 , 而一旦涉及到加锁保证线程安全 , 会对性能造成巨大的影响 . 而且对于不同的容器, 加锁方式的不同 , 性能可能也不同 ( 例如 hash 表的锁表和锁桶 ).
因此 STL 默认不是线程安全 . 如果需要在多线程环境下使用 , 往往需要调用者自行保证线程安全 .

对于 unique_ptr, 由于只是在当前代码块范围内生效, 因此不涉及线程安全问题.对于 shared_ptr, 多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数.

 其他常见的各种锁

悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。

乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。

CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试

自旋锁:使用自旋锁时,当多线程发生竞争锁的情况时,加锁失败的线程会忙等待(这里的忙等待可以用 while 循环等待实现),直到它拿到锁。而互斥锁加锁失败后,线程会让出 CPU 资源给其他线程使用,然后该线程会被阻塞挂起。如果成功申请临界资源的线程,临界区代码执行时间过长,自旋的线程会长时间占用 CPU 资源,所以自旋的时间和临界区代码执行的时间是成正比的关系。如果临界区代码执行的时间很短,就不应该使用互斥锁,而应该选用自旋锁。因为互斥锁加锁失败,是需要发生上下文切换的,如果临界区执行的时间比较短,那可能上下文切换的时间会比临界区代码执行的时间还要长。

 读者和写者问题:

 读者写者问题和生产者消费者模型的本质区别就是消费者会取走数据,而读者不会取走数据

读写锁接口

//初始化读写锁
pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);


//销毁读写锁
pthread_rwlock_destroy(pthread_rwlock_t *rwlock);


//读加锁
pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);


//写加锁
pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);


//解锁
pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/988160.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

牵手时代少年团,来伊份讲了一个“新鲜”故事

从数天前来伊份官方发布一组悬念九宫图海报开始&#xff0c;其新代言人的身份就呼之欲出。 9月7日上午9点&#xff0c;来伊份正式宣布时代少年团为全新品牌代言人。这一官宣在微博引爆并引发了轰动效应&#xff0c;同时代言人同款IP礼盒“伊份心选”正式发售。 图源&#xff1…

马拉松成绩训练利器“亚索800”

在马拉松火热的今天&#xff0c;跑圈中流行着各种各样的马拉松训练方法 其实在众多的流派体系中基本可归纳为两类&#xff1a; 1、强调基础有氧训练&#xff0c;以中低强度长时间跑步训练为主&#xff0c;LSD、MAF180、细胞分裂法都归属于这类训练&#xff1b; 2、强调高强度间…

大数据技术之Hadoop:Yarn集群部署(七)

目录 一、部署说明 二、集群规划 三、开始配置 3.1 MapReduce配置文件 3.2 YARN配置文件 3.3 分发配置文件 四、集群启停 4.1 命令介绍 4.2 演示 4.3 查看YARN的WEB UI页面 一、部署说明 Hadoop HDFS分布式文件系统&#xff0c;我们会启动&#xff1a; NameNode进…

帆软报表简单设置分组汇总

前言 接手之前同事留下的一个胶膜产品入库登记表&#xff08;帆软报表&#xff09;&#xff0c;结果手滑把原来的报表行删除了。用户反馈需要恢复按报表中 “型号” “卷数” 分组汇总一下 “件数” 字段的值、也就是按每种同型号同卷数分组、相同分组的要有一行汇总&#xf…

将 Qt Designer 的 ui 文件转换为 PySide2 使用的.py 文件

20201206 修订&#xff1a;在 Pyside2 的 5.15.2 版本中&#xff0c;从 ui 生成 py 文件过程&#xff0c;命令从 “uic” 变为 “pyside2-uic” Qt Designer 设计的 ui 文件&#xff0c;就是一个 xml 文件&#xff0c;通过 pyside-uic 可以转换成标准的.py 文件 。通过 QtDesi…

阿里云App备案详细流程_APP备案问题解答

阿里云APP备案流程分为6步&#xff0c;APP备案成功后应用可以上架&#xff0c;登录阿里云账号填写APP信息&#xff0c;等待阿里云初审&#xff0c;初审通过后进行工信部短信核验&#xff0c;管局审核通过后APP即可备案成功&#xff0c;最后移动APP应用可以分发平台上架&#xf…

习题练习 C语言(暑期第四弹)

自我小提升&#xff01; 前言一、数组二、指针运算三、统计每个月兔子的总数四、双指针的应用五、判断指针六、珠玑妙算七、两数之和八、数组下标九、指针十、寻找峰值十一、二级指针十二、大端小端十三、无符号参数十四、数对十五、截取字符串总结 前言 重要的事说三遍&#…

【校招VIP】JAVA语言考点之多线程实现相关

考点介绍&#xff1a; 多线程是JAVA校招中出现频度比较高的考点&#xff0c;本专题需要熟悉四种多线程的实现方式&#xff0c;并深入对比和区别。另外&#xff0c;对线程的状态和变化的相关题型也进行了分析。 JAVA语言考点之多线程实现相关-相关题目及解析内容可点击文章末尾…

CSS:实现文字溢出显示省略号且悬浮显示tooltip完整信息

组件&#xff1a; element ui中的tooltip组件 思路&#xff1a;通过ref获取宽度进行判断&#xff0c;当子级宽度大于对应标签/父级宽度显示tooltip组件 <div class"bechmark-wrap"><ul ref"bechmarkUl"><liv-for"(item,index) in comp…

SVN 索引版本与打包版本号不匹配

今天突然遇到了一个问题&#xff0c;SVN上传不了&#xff0c;错误提示如下&#xff1a; 解决方法&#xff1a; 1.其实&#xff0c;这是SVN库不小心搞坏了&#xff0c;只能重新再创建一个SVN仓库了。

Java jvm 内存溢出分析

1.如何分析jvm内存溢出呢 我们经常用visualVm监控Jvm的内存&#xff0c;cpu&#xff0c;线程的使用情况&#xff0c;通常可以根据内存不断增长来判断内存是否存在不释放。但是我们不可能时时盯着去看&#xff0c;这里涉及jvm堆内存配置&#xff0c;堆内存参数配置和调优会在其…

mongodb数据库操作

1、启动mongodb /usr/local/mongodb/bin/mongod --dbpath /var/mongodb/data/--logpath /var/mongodb/logs/log.log &在mongodb启动命令中 --dbpath 指定mongodb的数据存储路径 --logpath 指定mongodb的日志存储路径 2、停止mongodb 第一步先进入mongo命令行模式 第二…

nvm管理多个版本的nodejs

1. 已经安装过nodejs在安装nvm的步骤 1.安装nvmhttps://github.com/coreybutler/nvm-windows/releases 2.nvm安装位置 2.nvm管理的nodejs安装位置 4.最终的安装结构 备注&#xff1a;nodejs安装 2.使用nvm安装管理nodejs 2.1配置下载镜像&#xff1a; 找到nvm安装路径…

sonarqube版本升级

官方文档&#xff1a;Upgrade guide 步骤1、停止原有sonarqube服务&#xff0c;如果是docker部署的直接停掉容器并删除 步骤2、部署最新版sonarqube&#xff0c;保留原有配置 步骤3、访问sonarqube web 显示维护中&#xff0c;根据官方给出的升级方法&#xff0c;在sonarqub…

如何从公司或学校远程访问家里的电脑?

在某些情况下&#xff0c;我们需要远程访问另一台电脑&#xff0c;如在公司访问家里电脑的文件。那么&#xff0c;如何才能从公司或学校远程访问家里的电脑呢&#xff1f;这就需要用到远程访问软件了。 什么是远程访问软件&#xff1f; 远程访问软件是一种允许从本地电脑远…

python基础教程: while 循环语句用法

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 话不多说&#xff0c;直接开搞&#xff0c;如果有什么疑惑/资料需要的可以点击文章末尾名片领取源码 一.while 循环结构 特征: 减少代码冗余,提升代码效率 语法: while 条件表达式:code1code2...1.初始化一个变量 2.写上…

高速文件扫描仪:从繁琐到高效的革命性转变

高速文件扫描仪是办公设备中的重要一员&#xff0c;其主要功能是将纸质文件快速转换为数字格式&#xff0c;从而方便存储、传输和管理。那么&#xff0c;这个设备是如何起源并逐步发展起来的呢&#xff1f; 随着信息技术的不断推进&#xff0c;人们对数字化办公的需求变得越来…

Windows 10压缩卷参数错误怎么办?

压缩卷显示参数错误&#xff0c;这种情况很多用户都遇到过&#xff0c;遇到这种情况表示分区表信息与实际信息不符。因此&#xff0c;Windows不允许您对此驱动器进行任何更改&#xff0c;包括缩小卷大小。导致此问题的原因有很多&#xff0c;例如&#xff1a;病毒攻击、文件系统…

【验证码逆向专栏】房某下登录滑块逆向分析

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;不提供完整代码&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 本文章未…

《数字图像处理-OpenCV/Python》连载(5)图像的显示

《数字图像处理-OpenCV/Python》连载&#xff08;5&#xff09;图像的显示 本书京东优惠购书链接&#xff1a;https://item.jd.com/14098452.html 本书CSDN独家连载专栏&#xff1a;https://blog.csdn.net/youcans/category_12418787.html 第1章 图像的基本操作 为了方便初学…