《Linux从练气到飞升》No.31 多线程编程实践与线程安全技术

🕺作者: 主页

我的专栏
C语言从0到1
探秘C++
数据结构从0到1
探秘Linux
菜鸟刷题集

😘欢迎关注:👍点赞🙌收藏✍️留言

🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的很重要,有问题可在评论区提出,感谢阅读!!!

文章目录

    • 前言
    • 1 线程池
    • 2 基于队列的线程池实现代码
    • 3 线程安全的单例模式
      • 3.1 相关概念
      • 3.2 饿汉实现方式和懒汉实现方式
    • 4 STL、智能指针和线程安全
    • 5 其他常见的各种锁

前言

在当今软件开发领域,多线程编程已成为日益重要的技能之一。然而,要确保多线程程序的正确性和性能,并非易事。本篇博客旨在探讨多线程编程实践中的关键技术,从基于环形队列的生产者消费者模型,到线程池的实现和线程安全的单例模式,再到STL、智能指针和线程安全,以及其他常见的各种锁。

通过学习本文,读者将深入了解多线程编程的实际应用,掌握如何应对常见的并发编程挑战,并学会运用各种技术和方法来构建高效、稳定和可靠的多线程程序。让我们一同探索多线程编程的精髓,为未来的软件开发之路注入更多的智慧与创新。

1 线程池

  1. 什么是线程池?

简单来说,线程池就是有一堆已经创建好了的线程,初始它们都处于空闲等待状态,当有新任务需要处理的时候,就从这个池子里面取一个空闲等待的线程来处理该任务,当处理完成就再次把线程放回池中,以供后面的任务使用,当池子里面的线程都处于忙碌状态时,线程池中没有可用的空闲等待线程,此时,根据需要创建一个新的线程并置入池中,或通知任务线程池忙,稍后再试。

  1. 线程池存在的价值
  • 有任务时立马有线程进行服务,省掉了线程创建的时间
  • 可以有效防止服务器中线程过多导致系统过载的问题
  1. 线程池 vs 进程池
  • 线程池占用的资源更少,但是健壮性不强
  • 进程池占用的资源更多,但是健壮性很强

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

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

2 基于队列的线程池实现代码

makefile

main:main.cc
	g++ -o $@ $^ -lpthread
.PHONY:clean
clean:
	rm -f main

Thread_Pool.h

#pragma
#include<iostream>
#include<math.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#include<queue>

#define NUM 5
class Task
{
private:
    int _b;
public:
    Task(){}
    Task(int b)
        :_b(b)
    {}


    ~Task(){}

    void run(){
        std::cout<<"I am "<<pthread_self()<<" Task run ... bace:"<<\
            _b<<" ^2 = "<<pow(_b,2)<<std::endl;
        
    }
};

class Thread_Pool
{
private:
    std::queue<Task*> q;
    int _max_num;//线程总数

    pthread_mutex_t lock;
    pthread_cond_t cond;//只能让消费者操作

    void LockQueue(){
        pthread_mutex_lock(&lock);
    }

    void UnLockQueue(){
        pthread_mutex_unlock(&lock);
    }

    bool IsEmpty(){
        return q.size()==0;
    }

    bool IsFull(){
        return q.size()==_max_num;
    }

    void ThreadWait()
    {
        pthread_cond_wait(&cond,&lock);
    }

    void ThreadWakeUp()
    {
        pthread_cond_signal(&cond);
    }
public:
    Thread_Pool(int max_num = NUM)
        :_max_num(max_num)
    {}

    void Get(Task& out)//取数据
    {
        Task*t=q.front();
        q.pop();
        out=*t;
    }
    void Put(Task& in){//放置数据
        LockQueue();
        q.push(&in);
        UnLockQueue();
        ThreadWakeUp();
    }
    static void* Routine(void* arg){
        while(1){
            Thread_Pool* tp = (Thread_Pool*)arg;
            while(tp->IsEmpty()){
                tp->LockQueue();//静态成员方法不能访问非静态成员方法,所以传(void*)this过去
                tp->ThreadWait();//为空挂起等待
            }
            Task t;
            tp->Get(t);
            tp->UnLockQueue();
            t.run();
        }
    }

    void ThreadPoolInit(){
        pthread_mutex_init(&lock,NULL);
        pthread_cond_init(&cond,NULL);

        int i=0;
        pthread_t t;
        for(i=0;i<_max_num;++i){
            pthread_create(&t,NULL,Routine,(void*)this);  
        }
    }

    ~Thread_Pool(){
        pthread_mutex_destroy(&lock);
        pthread_cond_destroy(&cond);
    }
};

main.cc

#include"Thread_Pool.h"
using namespace std;

int main(){
    Thread_Pool *tp = new Thread_Pool();

    tp->ThreadPoolInit();

    while(1){
        int x=rand()%10 + 1;
        Task t(x);
        tp->Put(t);
        sleep(1);
    }
    return 0;
}

结果:
image.png

3 线程安全的单例模式

3.1 相关概念

  1. 什么是单例模式

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

  1. 什么是设计模式

IT行业这么火, 涌入的人很多. 俗话说林子大了啥鸟都有. 大佬和菜鸡们两极分化的越来越严重. 为了让菜鸡们不太拖大佬的后腿, 于是大佬们针对一些经典的常见的场景, 给定了一些对应的解决方案, 这个就是 设计模式

  1. 单例模式的特点

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

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

吃完饭, 立刻洗碗, 这种就是饿汉方式.。
因为下一顿吃的时候可以立刻拿着碗就能吃饭。
吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式。
懒汉方式最核心的思想是 “延时加载”. 从而能够优化服务器的启动速度。

  1. 饿汉方式实现单例模式
template <typename T>
class Singleton {
    static T data; //定义静态的类对象,程序加载类就加载对象
public:
    static T* GetInstance() {
    	return &data;
    }
};

只要通过 Singleton 这个包装类来使用 T 对象,,则一个进程中只有一个 T 对象的实例。

  1. 懒汉方式实现单例模式
template <typename T>
class Singleton {
	static T* inst;  //定义静态的类对象,程序运行时才加载对象
public:
    static T* GetInstance() {
        if (inst == NULL) {  
        	inst = new T(); 
        }
        return inst;
    }
};

存在一个严重的问题, 线程不安全。
第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例。
但是后续再次调用, 就没有问题了。

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

注意事项:

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

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

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

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

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

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

5 其他常见的各种锁

  • 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
  • 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。
  • CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。
  • 自旋锁,公平锁,非公平锁…

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构】图的存储结构(邻接矩阵)

一.邻接矩阵 1.图的特点 任何两个顶点之间都可能存在边&#xff0c;无法通过存储位置表示这种任意的逻辑关系。 图无法采用顺序存储结构。 2.如何存储图&#xff1f; 将顶点与边分开存储。 3.邻接矩阵&#xff08;数组表示法&#xff09; 基本思想&#xff1a; 用一个一维数…

Vatee万腾的科技征程:Vatee数字化创新的前沿探讨

在Vatee万腾的科技征程中&#xff0c;我们目睹了一场数字化创新的引领之旅&#xff0c;探讨了Vatee在科技前沿的独到见解。Vatee万腾不仅仅是一家科技公司&#xff0c;更是一支前行不辍的冒险队伍&#xff0c;通过不断突破自我&#xff0c;探索未知领域&#xff0c;引领着数字化…

Ghidra逆向工具配置 MacOS 的启动台显示(Python)

写在前面 通过 ghidra 工具, 但是只能用命令行启动, 不太舒服, 写个脚本生成 MacOS 的 app 格式并导入启动台. 不算复杂, 主要是解析包的一些元信息还有裁剪软件图标(通过 MacOS 自带的 API) 脚本 #!/opt/homebrew/bin/python3import os import re import subprocess as sp…

SpringCloud 2022有哪些变化

目录 前提条件 AOT支持 Spring Native支持 前提条件 Spring Cloud 2022.0.0是构建在Spring Framework 6.0和Spring Boot 3.0 之上的一S个主要版本。 JDK要求最低需要是Java 17J2EE要求最低需要Jakarta EE 9 AOT支持 Spring cloud 2022支持AOT编译&#xff0c;它是将程序源…

【HarmonyOS开发】配置开发工具DevEco Studio

1、下载 注意&#xff1a; 1、安装过程中&#xff0c;一定要自定义安装位置&#xff0c;包比较大&#xff0c;包比较大&#xff0c;包比较大&#xff01;&#xff01;&#xff01; 2、可以将该工具添加到右键中&#xff0c;否则&#xff0c;如果你的项目不是HarmonyOS&#xff…

Maven依赖传递和依赖冲突以及继承和聚合关系详解

Java全能学习面试指南&#xff1a;https://javaxiaobear.cn 1、Maven依赖传递和依赖冲突 1. Maven依赖传递特性 概念 假如有Maven项目A&#xff0c;项目B依赖A&#xff0c;项目C依赖B。那么我们可以说 C依赖A。也就是说&#xff0c;依赖的关系为&#xff1a;C—>B—>…

探索亚马逊大语言模型:开启人工智能时代的语言创作新篇章

文章目录 前言一、大语言模型是什么&#xff1f;应用范围 二、Amazon Bedrock总结 前言 想必大家在ChatGPT的突然兴起&#xff0c;大家多多少少都会有各种各样的问题&#xff0c;比如&#xff1a;大语言模型和生成式AI有什么关系呢&#xff1f;大语言模型为什么这么火&#xf…

linux使用chage修改用户密码过期时间解决rac安装互信问题

文章目录 一、RAC建多实例库提示互信问题二、原因分析1.修改系统用户密码期限2.修改语法&#xff1a;chage [选项] 用户名3.常用示例&#xff1a; 一、RAC建多实例库提示互信问题 二、原因分析 因为此次是在原有集群情况下创建多个实例&#xff0c;其实不需要优先排查俩节点的…

Aerial for Mac: 沉浸在高清鸟瞰的世界,让你的屏幕焕发新生

你是否已经厌倦了那些平淡无奇的屏保程序&#xff1f;是否希望你的Mac屏幕能更生动、更有趣&#xff1f;如果你对此抱有强烈的期待&#xff0c;那么Aerial for Mac绝对会是你期待已久的解决方案。 Aerial for Mac是一款独具特色的高清屏保程序&#xff0c;它以鸟瞰的视角带你领…

4.5 Windows驱动开发:实现进程数据转储

多数ARK反内核工具中都存在驱动级别的内存转存功能&#xff0c;该功能可以将应用层中运行进程的内存镜像转存到特定目录下&#xff0c;内存转存功能在应对加壳程序的分析尤为重要&#xff0c;当进程在内存中解码后&#xff0c;我们可以很容易的将内存镜像导出&#xff0c;从而更…

FISCO BCOS 3.0【01】搭建第一个区块链网络

官方技术文档&#xff1a;https://fisco-bcos-doc.readthedocs.io/zh-cn/latest/index.html 我们在官方技术文档的基础上&#xff0c;进行&#xff0c;对文档中一些不清楚的地方进行修正 搭建Air版本FISCO BCOS联盟链 本节以搭建单群组FISCO BCOS链为例操作&#xff0c;使用开…

关于ASO优化的分步入门指南1

欢迎阅读我们的应用商店优化&#xff08;ASO&#xff09;分步指南&#xff0c;接下来我们将引导大家完成ASO研究的初始步骤&#xff0c;为提高应用程序的知名度和吸引自然下载奠定基础。 1、确定竞争对手。 首先确定应用程序的直接和间接竞争对手。我们可以通过咨询客户或进行…

基于LeNet实现手写体数字识别实验

目录 1 数据 1.1 数据预处理 2 模型构建 2.1 自定义算子实现 2.2 Pytorch框架实现 2.3 测试两个网络的运算速度 2.4 两个网络加载同样的权重&#xff0c;两个网络的输出结果是否一致&#xff1f; 2.5 计算模型的参数量和计算量。 3 模型训练 4 模型评价 5 模型预测 总结…

深度学习基础知识——从人工神经网络开始

一、介绍 您知道第一个神经网络是在 20 世纪 50 年代初发现的吗&#xff1f; 深度学习 (DL) 和神经网络 (NN) 目前正在推动本世纪一些最巧妙的发明。他们从数据和环境中学习的令人难以置信的能力使他们成为机器学习科学家的首选。 深度学习和神经网络是自动驾驶汽车、图像识别软…

Pytorch torch.normal()的用法

该函数原型如下&#xff1a; normal(mean, std, *, generatorNone, outNone) 该函数返回从单独的正态分布中提取的随机数的张量&#xff0c;该正态分布的均值是mean&#xff0c;标准差是std。 用法如下&#xff1a;我们从一个标准正态分布N&#xff5e;(0,1)&#xff0c;提取…

《洛谷深入浅出基础篇》——P3405 citis and state ——哈希表

上链接&#xff1a;P3405 [USACO16DEC] Cities and States S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P3405 上题干&#xff1a; 题目描述 Farmer John 有若干头奶牛。为了训练奶牛们的智力&#xff0c;Farmer John 在谷仓的墙上放了一…

cadence virtuoso PEX option error

在设置PEX options时出现error。 Error while compiling rules file /home/IC/Tech/PDk_13mmrf_1P6M_30k/Calibre/LvS/SmicSPM7PR12R_cal013_mixR_sali_pimtx_1233_v2.6_2P . xrc: ErrorINCLi on lire 838of /home/IC/Tech/PDK_13mmrf_1P6M_30k/Calibre/LvS/SmicSPM7PR12R_cal…

DAO和增删改查通用方法-BasicDao

文章目录 一、BasicDao是什么&#xff1f;二、BasicDao分析三、BasicDao实现&#xff08;1&#xff09;BasicDao&#xff08;2&#xff09;ActorDao&#xff08;3&#xff09;TestDao 四、总结 一、BasicDao是什么&#xff1f; BasicDao:基础的数据对象&#xff0c;可以完成通用…

vmware workstation pro 17.5 安装 macos 13.5.2 虚拟机超详细图文教程

前言 本文很细&#xff0c;甚至有点墨迹&#xff0c;主要为了方便从来没用过 vmware 的新人&#xff0c;其实大部分步骤和正常安装虚拟机没有区别&#xff0c;详细贴图以方便大家对比细节 参考文章 感谢大佬们的无私分享 https://blog.csdn.net/qq_19731521/article/details…

【LeetCode刷题-滑动窗口】-- 795.区间子数组个数

795.区间子数组个数 class Solution {public int numSubarrayBoundedMax(int[] nums, int left, int right) {return lessEqualsThan(nums,right) - lessEqualsThan(nums,left - 1);}private int lessEqualsThan(int[] nums,int k){int len nums.length;int res 0,left 0,ri…