Linux知识点 -- Linux多线程(四)

Linux知识点 – Linux多线程(四)

文章目录

  • Linux知识点 -- Linux多线程(四)
  • 一、线程池
    • 1.概念
    • 2.实现
    • 3.单例模式的线程池
  • 二、STL、智能指针和线程安全
    • 1.STL的容器是否是线程安全的
    • 2.智能指针是否是线程安全的
  • 三、其他常见的各种锁
  • 四、读者写者问题
    • 1.读写锁
    • 2.读写锁接口


一、线程池

1.概念

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

  • 预先申请资源,用空间换时间;
  • 预先申请一批线程,任务到来就处理;
  • 线程池就是一个生产消费模型;

2.实现

thread.hpp
线程封装:

#pragma once#include<iostream>
#include<string>
#include<functional>
#include<cstdio>typedef void* (*fun_t)(void*); // 定义函数指针类型,后面回调class ThreadData  // 线程信息结构体
{
public:void* _args;std::string _name;
};class Thread
{
public:Thread(int num, fun_t callback, void* args): _func(callback){char nameBuffer[64];snprintf(nameBuffer, sizeof(nameBuffer), "Thread-%d", num);_name = nameBuffer;_tdata._args = args;_tdata._name = _name;}void start() // 创建线程{pthread_create(&_tid, nullptr, _func, (void*)&_tdata); // 直接将_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;
};

lockGuard.hpp
锁的封装,构建对象时直接加锁,对象析构时自动解锁;

#pragma once#include <iostream>
#include <pthread.h>class Mutex
{
public:Mutex(pthread_mutex_t *mtx): _pmtx(mtx){}void lock(){pthread_mutex_lock(_pmtx);}void unlock(){pthread_mutex_unlock(_pmtx);}~Mutex(){}private:pthread_mutex_t *_pmtx;
};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 4const char* gLevelMap[] = {"DEBUG","NORMAL","WARNING","ERROR","FATAL"
};#define LOGFILE "./threadpool.log"//完整的日志功能,至少需要:日志等级 时间 支持用户自定义(日志内容,文件行,文件名)void logMessage(int level, const char* format, ...)
{
#ifndef DEBUG_SHOWif(level == DEBUG) return;
#endifchar stdBuffer[1024];//标准部分time_t timestamp = time(nullptr);snprintf(stdBuffer, sizeof(stdBuffer), "[%s] [%ld] ", gLevelMap[level], timestamp);char logBuffer[1024];//自定义部分va_list args;va_start(args, format);vsnprintf(logBuffer, sizeof(logBuffer), format, args);va_end(args);FILE* fp = fopen(LOGFILE, "a");fprintf(fp, "%s %s\n", stdBuffer, logBuffer);fclose(fp);
}
  • 注:
    (1)提取可变参数
    在这里插入图片描述
    使用宏来提取可变参数:
    在这里插入图片描述
    将可变参数格式化打印到对应地点:
    在这里插入图片描述
    format是打印的格式;
    在这里插入图片描述
    (2)条件编译:
    在这里插入图片描述
    条件编译,不想调试的时候,就不加DEBUG宏,不打印日志信息;
    在这里插入图片描述
    -D:在命令行定义宏 ;

threadPool.hpp

线程池封装:

#include "thread.hpp"
#include <vector>
#include <queue>
#include <unistd.h>
#include "log.hpp"
#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;}ThreadPool(int thread_num = g_thread_num): _num(thread_num){pthread_mutex_init(&_lock, nullptr);pthread_cond_init(&_cond, nullptr);for (int i = 1; i <= _num; i++){_threads.push_back(new Thread(i, routine, this));// 线程构造传入的this指针,是作为ThreadData结构体的参数的,ThreadData结构体才是routine回调函数的参数}}void run(){for (auto &iter : _threads){iter->start();logMessage(NORMAL, "%s %s", iter->name().c_str(), "启动成功");}}// 消费过程:线程调用回调函数取任务就是所谓的消费过程,访问了临界资源,需要加锁static void *routine(void *args){ThreadData *td = (ThreadData *)args;ThreadPool<T> *tp = (ThreadPool<T> *)td->_args; // 拿到this指针while (true){T task;{lockGuard lockguard(tp->getMutex());while (tp->isEmpty()){tp->waitCond();}// 读取任务task = tp->getTask();// 任务队列是共享的,将任务从共享空间,拿到私有空间}task(td->_name); // 处理任务}}void pushTask(const T &task){lockGuard lockguard(&_lock); // 访问临界资源,需要加锁_task_queue.push(task);pthread_cond_signal(&_cond); // 推送任务后,发送信号,让进程处理}~ThreadPool(){for (auto &iter : _threads){iter->join();delete iter;}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;      // 条件变量
};
  • 注:
    (1)如果回调函数routine放在thread类里面,由于成员函数会默认传this指针,因此参数识别的时候可能会出错,所以需要设置成静态成员;在这里插入图片描述
    在这里插入图片描述
    (2)如果设置成静态类内方法,这个函数只能使用静态成员,而不能使用其他类内成员;
    可以让routine函数拿到整体对象,在构造线程的时候,routine的参数传入this指针;

    在这里插入图片描述
    在构造函数的初始化列表中是参数的初始化,在下面的函数体中是赋值的过程,因此在函数体中对象已经存在了,就可以使用this指针了;
    (3)类内公有接口让静态成员函数routine通过this指针能够访问类内成员;
    在这里插入图片描述
    testMain.cc
#include"threadPool.hpp"
#include"Task.hpp"
#include<ctime>
#include<cstdlib>
#include<iostream>
#include<unistd.h>int main()
{srand((unsigned long)time(nullptr) ^ getpid());ThreadPool<Task>* tp = new ThreadPool<Task>();tp->run();while(true){//生产的时候,只做任务要花时间int x = rand()%100 + 1;usleep(7756);int y = rand()%30 + 1;Task t(x, y, [](int x, int y)->int{return x + y;});logMessage(DEBUG, "制作任务完成:%d+%d=?", x, y);//推送任务到线程池中tp->pushTask(t);sleep(1);}return 0;
}

运行结果:
在这里插入图片描述

3.单例模式的线程池

threadPool.hpp

#include "thread.hpp"
#include <vector>
#include <queue>
#include <unistd.h>
#include "log.hpp"
#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 = 1; i <= _num; i++){_threads.push_back(new Thread(i, routine, this));// 线程构造传入的this指针,是作为ThreadData结构体的参数的,ThreadData结构体才是routine回调函数的参数}}ThreadPool(const ThreadPool<T> &other) = delete;const ThreadPool<T>& operator=(const ThreadPool<T> &other) = delete;public://创建单例对象的类内静态成员函数static ThreadPool<T>* getThreadPool(int num = g_thread_num){//在这里再加上一个条件判断,可以有效减少未来必定要进行的加锁检测的问题//拦截大量的在已经创建好单例的时候,剩余线程请求单例而直接申请锁的行为if(nullptr == _thread_ptr){//加锁lockGuard lockguard(&_mutex);//未来任何一个线程想要获取单例,都必须调用getThreadPool接口//一定会存在大量的申请锁和释放锁的行为,无用且浪费资源if(nullptr == _thread_ptr){_thread_ptr = new ThreadPool<T>(num);}}return _thread_ptr;}void run(){for (auto &iter : _threads){iter->start();logMessage(NORMAL, "%s %s", iter->name().c_str(), "启动成功");}}// 消费过程:线程调用回调函数取任务就是所谓的消费过程,访问了临界资源,需要加锁static void *routine(void *args){ThreadData *td = (ThreadData *)args;ThreadPool<T> *tp = (ThreadPool<T> *)td->_args; // 拿到this指针while (true){T task;{lockGuard lockguard(tp->getMutex());while (tp->isEmpty()){tp->waitCond();}// 读取任务task = tp->getTask();// 任务队列是共享的,将任务从共享空间,拿到私有空间}task(td->_name); // 处理任务}}void pushTask(const T &task){lockGuard lockguard(&_lock); // 访问临界资源,需要加锁_task_queue.push(task);pthread_cond_signal(&_cond); // 推送任务后,发送信号,让进程处理}~ThreadPool(){for (auto &iter : _threads){iter->join();delete iter;}pthread_mutex_destroy(&_lock);pthread_cond_destroy(&_cond);}private:std::vector<Thread *> _threads; // 线程池int _num;std::queue<T> _task_queue; // 任务队列static ThreadPool<T>* _thread_ptr;static pthread_mutex_t _mutex;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;

在这里插入图片描述
在这里插入图片描述
多线程同时调用单例过程,由于创建过程是非原子的,有可能被创建多个对象,是非线程安全的;
需要对创建对象的过程加锁,就可以保证在多线程场景当中获取单例对象;
但是未来任何一个线程想调用单例对象,都必须调用这个成员函数,就会存在大量申请和释放锁的行为;
可以在之间加一个对单例对象指针的判断,若不为空,就不进行对象创建;

在这里插入图片描述
testMain.cc

#include"threadPool.hpp"
#include"Task.hpp"
#include<ctime>
#include<cstdlib>
#include<iostream>
#include<unistd.h>int main()
{srand((unsigned long)time(nullptr) ^ getpid());//ThreadPool<Task>* tp = new ThreadPool<Task>();//tp->run();    ThreadPool<Task>::getThreadPool()->run();//创建单例对象while(true){//生产的时候,只做任务要花时间int x = rand()%100 + 1;usleep(7756);int y = rand()%30 + 1;Task t(x, y, [](int x, int y)->int{return x + y;});logMessage(DEBUG, "制作任务完成:%d+%d=?", x, y);//推送任务到线程池中ThreadPool<Task>::getThreadPool()->pushTask(t);sleep(1);}return 0;
}

运行结果:
在这里插入图片描述

二、STL、智能指针和线程安全

1.STL的容器是否是线程安全的

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

2.智能指针是否是线程安全的

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

三、其他常见的各种锁

  • 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等) ,当其他线程想要访问数据时,被阻塞挂起;
  • 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作;
    CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试;
  • 自旋锁
    临界资源就绪的时间决定了线程等待的策略;
    不断检测资源是否就绪就是自旋(轮询检测);
    自旋锁本质就是通过不断检测锁状态,来检测资源是否就绪的方案

    在这里插入图片描述
    互斥锁是检测到资源未就绪,就挂起线程;
    临界资源就绪的时间决定了使用哪种锁;

四、读者写者问题

1.读写锁

在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少,相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时长。给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门]处理这种多读少写的情况呢?有,那就是读写锁。

  • 读者写者模型与生产消费模型的本质区别:
    生产消费模型中消费者会取走数据,而读者写者模型中读者不会取走数据;

  • 读锁的优先级高

2.读写锁接口

  • 初始化:
    在这里插入图片描述

  • 读者加锁:
    在这里插入图片描述

  • 写者加锁:

在这里插入图片描述
生产消费模型中,生产者和消费者的地位是对等的,这样才能达到最高效的状态
而读写者模型中,写者只有在读者全部退出的时候才能写,是读者优先的,这样就会发生写者饥饿问题;
读者写者问题中读锁的优先级高,是因为这种模型的应用场景为:数据的读取频率非常高,而被修改的频率特别低,这样有助于提升效率;

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微服务dubbo和nexus

微服务是一种软件开发架构风格&#xff0c;它将一个应用程序拆分成一组小型、独立的服务&#xff0c;每个服务都可以独立部署、管理和扩展。每个服务都可以通过轻量级的通信机制&#xff08;通常是 HTTP/REST 或消息队列&#xff09;相互通信。微服务架构追求高内聚、低耦合&am…

vue声明周期

1.在created中发送数据 async created(){ const resawait axios.get("url) this.listres.data.data } 2.在mounted中获取焦点 mounted(){ document.querySelector(#inp).focus()

视频图像处理算法opencv在esp32及esp32s3上面的移植,也可以移植openmv

opencv在esp32及esp32s3上面的移植 Opencv简介 OpenCV是一个基于Apache2.0许可&#xff08;开源&#xff09;发行的跨平台计算机视觉和机器学习软件库&#xff0c;可以运行在Linux、Windows、Android和Mac OS操作系统上&#xff0c;它轻量级而且高效——由一系列 C 函数和少量…

vmware虚拟机(ubuntu)远程开发golang、python环境安装

目录 1. 下载vmware2. 下载ubuntu镜像3. 安装4. 做一些设置4.1 分辨率设置4.2 语言下载4.3 输入法设置4.4 时区设置 5. 直接切换管理员权限6. 网络6.1 看ip6.2 ssh 7. 本地编译器连接远程服务器7.1 创建远程部署的配置7.2 文件同步7.3 远程启动项目 8. ubuntu安装golang环境8.1…

TDesign表单rules通过函数 实现复杂逻辑验证输入内容

Element ui 中 我们可以通过validator 绑定函数来验证一些不在表单model中的值 又或者处理一下比较复杂的判断逻辑 TDesign也有validator 但比较直观的说 没有Element那么好用 这里 我们给validator绑定了我们自己的checkAge函数 这个函数中 只有一个参数 value 而且 如果你的…

Java-Optional类

概述 Optional是JAVA 8引入的一个类&#xff0c;用于处理可能为null的值。 利用Optional可以减少代码中if-else的判断逻辑&#xff0c;增加代码的可读性。且可以减少空指针异常的发生&#xff0c;增加代码的安全性。 常用的方法 示例 代码 public class OptionalTest {pub…

前端基础(Element、vxe-table组件库的使用)

前言&#xff1a;在前端项目中&#xff0c;实际上&#xff0c;会用到组件库里的很多组件&#xff0c;本博客主要介绍Element、vxe-table这两个组件如何使用。 目录 Element 引入element 使用组件的步骤 使用对话框的示例代码 效果展示 vxe-table 引入vxe-table 成果展…

【力扣】62. 不同路径 <动态规划>

【力扣】62. 不同路径 一个机器人位于一个 m m m x n n n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。问总共有多少条…

详解 SpringMVC 的 @RequestMapping 注解

文章目录 1、RequestMapping注解的功能2、RequestMapping注解的位置3、RequestMapping注解的value属性4、RequestMapping注解的method属性5、RequestMapping注解的params属性&#xff08;了解&#xff09;6、RequestMapping注解的headers属性&#xff08;了解&#xff09;7、Sp…

2023谷歌开发者大会直播大纲「终稿」

听人劝、吃饱饭,奉劝各位小伙伴,不要订阅该文所属专栏。 作者:不渴望力量的哈士奇(哈哥),十余年工作经验, 跨域学习者,从事过全栈研发、产品经理等工作,现任研发部门 CTO 。荣誉:2022年度博客之星Top4、博客专家认证、全栈领域优质创作者、新星计划导师,“星荐官共赢计…

无涯教程-JavaScript - CUBEMEMBERPROPERTY函数

描述 CUBEMEMBERPROPERTY函数从多维数据集返回成员属性的值。使用此函数可以验证多维数据集中是否存在成员名称,并返回该成员的指定属性。 语法 CUBEMEMBERPROPERTY (connection, member_expression, property)争论 Argument描述Required/OptionalconnectionName of the co…

ORB-SLAM3复现过程中遇到的问题及解决办法

在复现过程中遇到的问题的解决过程 1. 版本检查1.1 Opencv版本的检测1.2 Eigen版本的检测1.3 查看Python版本1.4 其他 2. 编译过程中遇到的问题及解决办法2.1 ./build.sh遇到的问题2.2 ./build_ros.sh遇到的问题 因为环境比较干净&#xff0c;所以遇到的问题相对少一些&#xf…

【LeetCode】409. 最长回文串

409. 最长回文串&#xff08;简单&#xff09; 方法&#xff1a;哈希表 贪心 思路 不难发现&#xff0c;回文字符串一定是由 若干偶数个字符 至多一个奇数个字符 组成 。我们可以使用一个长度为 128 的 hash表来记录每一个字符的出现次数&#xff0c;当该字符出现了两次&am…

【learnopengl】Assimp构建与编译

文章目录 【learnopengl】Assimp构建与编译1 前言2 Assimp构建与编译2.1 下载源码2.2 CMake构建2.3 VS2022编译 3 在VS中配置Assimp库4 验证 【learnopengl】Assimp构建与编译 1 前言 最近在跟着LearnOpenGL这个网站学习OpenGL&#xff0c;这篇文章详细记录一下教程中关于Ass…

Navicat介绍及下载安装教程

Navicat是一个广泛使用的数据库管理工具&#xff0c;可用于管理多种数据库系统&#xff0c;如MySQL、MariaDB、Oracle等。它提供了丰富的功能&#xff0c;使得管理数据库变得更加容易和高效。安装Navicat十分简单&#xff0c;只需下载安装包并按照向导进行操作即可。在安装完成…

RealVNC配置自定义分辨率(AlmaLinux 8)

RealVNC 配置自定义分辨率&#xff08;AlmaLinux8&#xff09; 参考RealVNC官网 how to set up resolution https://help.realvnc.com/hc/en-us/articles/360016058212-How-do-I-adjust-the-screen-resolution-of-a-virtual-desktop-under-Linux-#standard-dummy-driver-0-2 …

【ROS 04】ROS运行管理

ROS是多进程(节点)的分布式框架&#xff0c;一个完整的ROS系统实现&#xff1a; 可能包含多台主机&#xff1b; 每台主机上又有多个工作空间(workspace)&#xff1b; 每个的工作空间中又包含多个功能包(package)&#xff1b; 每个功能包又包含多个节点(Node)&#xff0c;不同的…

UE4/5在蓝图细节面板中添加函数按钮(蓝图与c++的方法)

目录 在细节面板中添加按钮使用函数 蓝图的方法 事件 函数 效果 uec的方法 效果 在细节面板中添加按钮使用函数 很多时候&#xff0c;我们可以看到一些插件的actor类中&#xff0c;点击一下之后就可以实现如矩阵一样的效果。 实际上是因为其使用了函数来修改了蓝图中的数…

ROS-4.创建发布者和订阅者

ros中非长连接的通信使用topic的方式&#xff0c;publisher向topic发布消息&#xff0c;subscriber订阅topic消息&#xff0c;对于非应答模式的通信适合使用该模式&#xff0c;如下图 接下来我们实现一个发布者和订阅者 1. 创建功能包 在实现订阅者和发布者的时候我们需要先…

wap2app 隐藏系统状态栏

一、首先创建wap2App项目 1、文件》新建》项目 2、选择Wap2App项目&#xff1a;输入项目名称、网站首页地址&#xff08;如果是本地localhost的话改为你的IP地址即可&#xff09;&#xff0c;点击创建 二、创建完wap2App项目后 隐藏系统状态栏只要修改1、2选项即可 1、找到根…