【Linux】多线程的相关知识点
一、线程安全
1.1 可重入 VS 线程安全
1.1.1 概念
- 线程安全:多个线程并发执行同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁的保护的情况下,会出现问题。
- 重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行力再次进入,一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函称之为可重入函数,否则为不可重入函数。
- 线程安全是线程在执行中的相互关系,重入是函数的特点
- 引起线程安全有很多种情况,重入是其中的一种
1.1.2 常见的线程不安全的情况
- 不保护共享变量的函数
- 函数状态随着被调用,状态发生变化的函数
- 返回指向静态变量指针的函数
- 调用线程不安全函数的函数
1.1.3 对函数状态随着被调用,状态发生变化进行解释
class A
{
public:void fun(){std::cout << "fun" << std::endl;}
}class B : public class A
{int count = 0;
public:void test(){fun();count++;std::cout << count << std::endl;}
}
1.2 常见的线程安全的情况
- 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说,这些线程是安全的
- 类或者接口对于线程来说都是原子操作
- 多个线程之间的切换不会导致该接口的执行结果存在二义性
1.3 常见不可重入的情况
- 调用了malloc/free函数,因为malloc函数是用全局链表带管理堆的
- 调用了标准的I/O库函数,标准的I/O库函数的很多实现都以不可重入的方式使用全局数据结构
- 可重入函数体内使用了静态的数据结构
1.4 常见的可重入的情况
- 不使用全局变量或静态变量
- 不使用malloc/free开辟的空间
- 不调用不可重入函数
- 不返回静态或去全局数据,所有数据都有函数的调用者提供
- 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据
1.5 可重入与线程安全的联系
- 函数是可重入的,那就是线程安全的
- 线程安全不一定是可重入,那就不能有多个线程使用,有可能引发线程安全问题
- 如果一个函数中有全局变量,那么这个函数既不是线程安全的,也不是可重入的
1.6 可重入与线程安全的区别
- 可重入函数是线程安全函数的一种
- 线程安全不一定是可重入的,而可重入函数则一定是线程安全的
- 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放,则会产生死锁,因此是不可重入的。
二、常见锁的概念
2.1 死锁的概念:
死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于一种永久等待状态。
2.2 出现死锁的场景:
- 在加锁之后,又进行了一次加锁操作
- 现在有两个线程:线程A和线程B。两个线程都要互相申请两个锁才能进行继续访问,但是由于访问的顺序不同,会造成死锁的现象
2.3 死锁的四个必要条件:(?????)
- 互斥条件:一个资源每次只能被一个执行流使用
- 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:一个执行流已经获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干个执行流之间形成一种头尾相接的循环等待资源的关系
2.4 避免死锁:
- 破坏死锁的四个必要条件
- 加锁顺序一致
- 避免锁未释放的场景
- 资源一次性分配
三、Linux线程同步
3.1 条件变量
- 当一个线程互斥地访问某个变量时,它可能发现在其他线程改变状态之前,他什么也做不了
- 例如一个线程访问队列时,发现队列为空,它只能等待,直到其他线程将一个节点添加到队列中。这种情况就需要使用到条件变量
3.2 同步概念与竞态条件
- 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步
- 竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件,在线程场景下,这种问题也不难理解
四、STL、智能指针和线程安全
4.1 STL中的容器是否是线程安全的
STL中的容器不是线程安全的,因为STL的设计初衷是将性能挖掘到极致,而一旦涉及到加锁保证线程安全,会对性能造成巨大的影响,而且对于不同的容器,加锁方式的不同,性能也可能不同(例如hash表的锁表和锁桶)
因此STL默认不是线程安全的,如果需要在多线程环境下使用,往往需要调用者自行保证线程安全。
4.2 智能指针是否是线程安全的
对于unique_ptr,由于只是在当前代码块范围内生效,因此不涉及线程安全问题
对于shared_ptr,多个对象需要共用一个引用计数变量,所以会存在线程安全问题,但是标准库实现的时候考虑到这个问题,基于原子操作的方式保证shared_ptr能够足够高效,原子的操作引用计数。
五、线程安全的单例模式(有待学习)
5.1 什么是单例模式
单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例。
5.2 单例模式的特点
某些类,只应该具有一个对象(实例),就称之为单例。例如一个男人只能有一个媳妇。
在很多服务器开发场景中,经常需要让服务器加载很多的数据到内存中,往往需要用一个单例的类来管理这些数据。
5.3 饿汉实现方式和懒汉实现方式
举个例子:
- 吃完饭,立刻洗碗,这种就是饿汉方式。因为下一顿吃的时候可以立刻拿着碗就能吃饭
- 吃完饭,先把碗放下,然后下一顿饭用到了这个碗再洗这个碗,这就是懒汉方式。
懒汉方式最核心的思想是:延时加载,从而能够优化服务器的启动速度。
5.3.1 饿汉方式实现单例模式
template<typename T>
class Singleton{static T date;
public:static T* GetTnstance() {return &date;}
}
// 只要通过Singleton这个包装类来使用T对象,则一个进程中只有一个T对象的实例
5.3.2 懒汉方式实现单例模式
template<typename T>
class Singleton
{static T* inst;
public:static T* GetInstance(){if(inst == nullptr){inst = new T();}return inst;}
};
存在一个严重的问题,线程不安全。如果在第一次调用GetInstance的时候, 两个线程同时调用,可能会创建出两份T对象的实例,但是后续再次调用,就没有问题了。
5.4 将线程池改为懒汉方式实现单例模式(线程安全版本)
// 添加单例
static ThreadPool<T> *_instance;
static pthread_mutex_t _lock;template <class T>
ThreadPool<T> *ThreadPool<T>::_instance;template <class T>
pthread_mutex_t ThreadPool<T>::_lock = PTHREAD_MUTEX_INITIALIZER;
static ThreadPool<T> *GetInstance()
{// 如果是多线程调用以下的代码就会有问题// 所以我们需要进行加锁// 利用双判断的方式,可以有效减少获取单例的加锁成本,而且保证线程安全// 保证第二次之后,所有的线程不用在加锁,直接返回if (nullptr == _instance){LockGuard lockguard(&_lock);if (nullptr == _instance){_instance = new ThreadPool<T>;_instance->InitThreadPool();_instance->Start();LOG(DEBUG, "创建线程池单例");return _instance;}}LOG(DEBUG, "获取线程池单例");return _instance;
}
// 赋值拷贝警用
ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;
ThreadPool(const ThreadPool<T> &) = delete;
六、其他常见的各种锁
6.1 悲观锁
在每次读取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起
6.2 乐观锁
每次取数据的时候,总是乐观的认为数据不会被其他线程修改,因此不上锁,但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改,主要采用两种方式:版本号机制和CAS操作。
6.2.1 版本号机制
一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
6.2.2 CAS操作
当需要更新数据时,判断当前内存值和之前取得的值是否相等,如果相等,则用新值更新;如果不相等,则失败。失败之后,需要进行重试,一般是一个自旋过程,即不断重试。
6.3 自旋锁
在之前的学习中,我们从来没有讨论过在临界区里线程执行的时长问题:如果时间比较久:推荐其他线程阻塞挂起等待;如果时间比较短:推荐其他线程不要休眠阻塞挂起,而是不断一直抢占锁,直到申请成功(自旋)。
自旋的过程中,用户会发现自旋锁和之前学习的互斥锁在行为上是相似的,都是阻塞在那里。
七、读者写者问题
7.1 引入读者写者问题
读者写者问题的例子:写文章,打印报纸、杂志,出黑板报
- 读者总多,写者较少——读者写者问题最常见的情况
- 有线程向公共资源中写入,其他线程从公共资源中读取数据——读者写者问题
7.1.1 321 原则
- 3种关系:读者与读者(没有关系),写者与写者(互斥),读者与写者(互斥和同步)
- 2种角色:读者,写者
- 1种场景:公共资源
7.1.2 生产者消费者模型与读者写者问题的本质区别
- 读者和消费者的本质区别:消费者会把数据拿走,而读者不会把数据拿走,只会进行拷贝
7.2 模拟实现一下读者写者的加锁逻辑
对于公共资源来说,创建一个全局变量,读者锁和写者锁。但是在实际中,只要一个读者锁。
int reader_count = 0;
pthread_mutex_t wlock;
pthread_mutex_t rlock;
对于读者来说:
lock(&rlock); // 先将读者加锁
if(reader_count == 0)
{lock(&wlock); // 变量为空,说明第一次读,将写者加锁// 这种操作只会进行一次,否则就有死锁//如果申请成功,继续运行,不会有任何读者进来//如果申请失败,阻塞
}
++ reader_count;
unlock(&rlock);// 开始进行常规的readlcok(&rlock);
--read_count;
if(read_count == 0) // 如果读者数量为0,则可以唤醒写者
{unlock(&wlock);
}
unlock(&rlock);
对于写者来说:
lock(&wlock);// 写入操作unlock(&wlock);
7.3 了解一下系统中读写锁的接口
7.3.1 初始化读写锁
函数的原型:
#include <pthread.h>int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t* restrict attr);
函数的功能:
进行初始化读写锁
函数的参数:
- rwlock:指向创建的读写锁对象
- attr:属性,一般置为nullptr
函数的返回值:
- 成功返回 0, 失败直接返回错误号
7.3.2 销毁读写锁
函数的原型:
#include <pthread.h>int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
函数的功能:
将所创建好的读写锁进行销毁
函数的参数:
- rwlock:执行所要销毁的读写锁的指针
函数的返回值:
- 成功返回 0, 失败直接返回错误号
7.3.3 给读者锁加锁
函数的原型:
#include <pthread.h>int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
7.3.4 给写者锁加锁
函数的原型:
#include <pthread.h>int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
7.3.5 给读者锁和写者锁解锁
函数的原型:
#include <pthread.h>int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
7.3.6 代码部分
#include <pthread.h>
#include <stdio.h>
#include <iostream>// 读写锁的概念int count = 0; // 共享资源
pthread_rwlock_t rwlock; // 创建一个读写锁#define NUM 5// 读者
void *reader(void *arg)
{pthread_rwlock_rdlock(&rwlock); // 给读者加锁std::cout << "Reader conut:" << count << std::endl;pthread_rwlock_unlock(&rwlock); // 进行解锁return nullptr;
}// 写者
void *writer(void *arg)
{pthread_rwlock_wrlock(&rwlock); // 给写者加锁count++;pthread_rwlock_unlock(&rwlock); // 给读者解锁return nullptr;
}int main()
{pthread_t reader_threads[NUM], writer_threads;pthread_rwlock_init(&rwlock, nullptr); // 给读写锁进行初始化pthread_create(&writer_threads, nullptr, writer, nullptr);for (int i = 0; i < NUM; i++){pthread_create(&reader_threads[i], nullptr, reader, nullptr);}pthread_join(writer_threads, nullptr);for (int i = 0; i < NUM; i++){pthread_join(reader_threads[i], nullptr);}pthread_rwlock_destroy(&rwlock);return 0;
}
相关文章:
【Linux】多线程的相关知识点
一、线程安全 1.1 可重入 VS 线程安全 1.1.1 概念 线程安全:多个线程并发执行同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁的保护的情况下,会出现问题。重入:同一个函数被不同…...
Java反射详解
Java反射 一.什么是反射 我们使用的一些像框架,tomcat,或者一些其他的组件(jackson 对象–>json)。他们可以做到给他什么类名,就可以创建给定类的对象,并调用该对象的方法和属性。这是如何做到的? 当他们加载我们…...
Spring Boot与Apache Kafka集成的深度指南
Spring Boot与Apache Kafka集成的深度指南 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿! 在现代分布式系统中,消息队列的作用愈发重要࿰…...
甄选版“论软件系统架构评估”,软考高级论文,系统架构设计师论文
论文真题 对于软件系统,尤其是大规模的复杂软件系统来说,软件的系统架构对于确保最终系统的质量具有十分重要的意义,不恰当的系统架构将给项目开发带来高昂的代价和难以避免的灾难。对一个系统架构进行评估,是为了:分析现有架构存在的潜在风险,检验设计中提出的质量需求,…...
uniapp开发企业微信内部应用
最近一直忙着开发项目,终于1.0版本开发完成,抽时间自己总结下在项目开发中遇到的技术点。此次项目属于自研产品,公司扩展业务,需要在企业微信中开发内部应用。因为工作中使用的是钉钉,很少使用企业微信,对于…...
0122__linux之eventfd理解
linux之eventfd理解-CSDN博客 Linux fd 系列 — eventfd 是什么?-CSDN博客...
数学建模 —— 查找数据
目录 百度搜索技巧 完全匹配搜索:查询词的外边加上双引号“ ” 标题必含关键词:查询词前加上intitle: 搜索文档:空格再输入filetype:文件格式 去掉不想要的:查询词后面加空格后加减号与关键字 知网查文献 先看知网的硕博士…...
合并有序链表
合并有序链表 图解代码如下 图解 虽然很复杂,但能够很好的理解怎么使用链表,以及对链表的指针类理解 代码如下 Node* merge_list_two_pointer(List& list1, List& list2) {Node* new_head1 list1.head;Node* new_head2 list2.head;Node* s…...
【SpringBoot Web框架实战教程】05 Spring Boot 使用 JdbcTemplate 操作数据库
不积跬步,无以至千里;不积小流,无以成江海。大家好,我是闲鹤,微信:xxh_1459,十多年开发、架构经验,先后在华为、迅雷服役过,也在高校从事教学3年;目前已创业了…...
Spark基于DPU的Native引擎算子卸载方案
1.背景介绍 Apache Spark(以下简称Spark)是一个开源的分布式计算框架,由UC Berkeley AMP Lab开发,可用于批处理、交互式查询(Spark SQL)、实时流处理(Spark Streaming)、机器学习&a…...
Mini2440 start.s 修改支持串口输出,方便调试 (四)
经常会遇到点板子的时候,板子没有任何反应!怎么知道板子有没有在正常启动,在uboot阶段 start.s 中加入串口打印信息是很有必要的! 输出串口信息 ***UART:mini-2440-uBoot*** ***UART:mini-2440-uBoot*** ***UART:mini-2440-uBoo…...
【教程】几种不同的RBF神经网络
本站原创文章,转载请说明来自《老饼讲解-机器学习》www.bbbdata.com 目录 一、经典RBF神经网络1.1.经典径向基神经网络是什么1.2.经典径向基神经网络-代码与示例 二、广义回归神经网络GRNN2.1.广义回归神经网络是什么2.2.广义回归神经网络是什么-代码与示例 三、概率…...
【Liunx-后端开发软件安装】Liunx安装FDFS并整合nginx
【Liunx-后端开发软件安装】Liunx安装nacos 文章中涉及的相关fdfs相关软件安装包请点击下载: https://download.csdn.net/download/weixin_49051190/89471122 一、简介 FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括…...
【Java笔记】Flyway数据库管理工具的基本原理
文章目录 1. 工作流程2. 版本号校验算法3. 锁机制3.1 为什么数据库管理工具需要锁3.2 flyway的锁机制 Reference 最近实习做的几个项目都用到了Flyway来做数据库的版本管理,顺便了解了下基本原理,做个记录。 详细的使用就不写了,网上教程很多…...
国际数字影像产业园创业培训,全面提升创业能力!
国际数字影像产业园作为数字影像产业的创新高地,致力于提供全面的创业支持服务。其中,创业培训作为重要的组成部分,旨在通过系统的课程设置和专业的讲师团队,为创业者提供从基础到进阶的全方位指导,帮助他们在数字影像…...
pyqt5 制作视频剪辑软件,切割视频
该软件用于切割视频,手动选取视频片段的起始帧和结束帧并保存为json文件。gui界面如下:包含快进、快退、暂停等功能, 代码如下: # codingUTF-8 """ theme: pyqt5实现动作起始帧和结束帧的定位,将定位到…...
VUE----通过nvm管理node版本
使用 NVM(Node Version Manager)来管理和切换 Node.js 版本是一个很好的选择。以下是在 苹果电脑macos系统 上使用 NVM 安装和切换 Node.js 版本的步骤: 1. 安装 NVM 如果你还没有安装 NVM,可以按照以下步骤进行安装:…...
R语言进行字符的替换和删减gsub,substr函数
目录 R语言读文件“-“变成“.“ 提取列字符前几个 提取列字符末尾几个 进行字母替换 paste0函数使用 长宽数据转换 R语言读文件“-“变成“.“ R语言读文件“-“变成“.“_r语言 列名中的-变成了点-CSDN博客 怎样将"."还原为"-" rm(list = ls()…...
2024年6月27日,欧盟REACH法规新增第31批1项SVHC高关注物质
ECHA公布第31批1项SVHC,物质已增至241项 2024年6月27日,ECHA公布第31批1项SVHC,总数达241项。新增物质未包括磷酸三苯酯,仍在评议中。REACH法规要求SVHC含量超0.1%需告知下游,出口超1吨须通报ECHA。SCIP通报要求SVHC含…...
高通410-linux棒子设置网络驱动
1.首先打开设备管理器 2.看到其他设备下的RNDIS,右键更新驱动程序 3.点击浏览我的电脑… 最后一个...
PostgreSQL的系统视图pg_stat_archiver
PostgreSQL的系统视图pg_stat_archiver 在 PostgreSQL 数据库中,pg_stat_archiver 视图提供了关于归档进程(archiver process)的统计信息。归档进程负责将 WAL(Write-Ahead Logging)日志文件复制到归档存储࿰…...
【D3.js in Action 3 精译】第一部分 D3.js 基础知识
第一部分 D3.js 基础知识 欢迎来到 D3.js 的世界!可能您已经迫不及待想要构建令人惊叹的数据可视化项目了。我们保证,这一目标很快就能达成!但首先,我们必须确保您已经掌握了 D3.js 的基础知识。这一部分提到的概念将会在您后续的…...
面试经验分享 | 渗透测试工程师(实习岗)
所面试的公司:某安全厂商 所在城市:南京 面试职位:渗透测试工程师实习岗位 面试过程: 腾讯会议(视频) 面试过程:整体流程就是自我介绍加上一些问题问题balabalabala。。。由于面的岗位是渗透…...
STM32 IWDG(独立看门狗)
1 IWDG简介 STM32有两个看门狗:一个是独立看门狗(IWDG),另外一个是窗口看门狗。独立看门狗也称宠物狗,窗口看门狗也称警犬。本文主要分析独立看门狗的功能和它的应用。 独立看门狗用通俗一点的话来解释就是一个12位的…...
ios swift5 获取wifi列表
参考博客: iOS之Wifi开发探究 - 稀土掘金 iOS 无法获取 WiFi 列表?一定是因为你不知道这个框架 - 稀土掘金 iOS获取Wifi列表详解 - 简书...
回溯法c++学习 解决八皇后问题
使用回溯法解决八皇后问题 八皇后问题是一个以国际象棋为背景的问题:如何能够在88 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。这…...
5. Spring IoCDI ★ ✔
5. Spring IoC&DI 1. IoC & DI ⼊⻔1.1 Spring 是什么?★ (Spring 是包含了众多⼯具⽅法的 IoC 容器)1.1.1 什么是容器?1.1.2 什么是 IoC?★ (IoC: Inversion of Control (控制反转))总…...
数据库自动备份到gitee上,实现数据自动化备份
本人有个不太好的习惯,每次项目的数据库都是在线上创建,Navicat 连接线上数据库进行处理,最近有一个项目需要二次升级,发现老项目部署的服务器到期了,完蛋,数据库咩了!!!…...
探索 Spring Cloud Gateway:构建微服务架构的关键一环
1. 简介 在当今的分布式系统中,微服务架构已经成为了一种流行的架构模式。在微服务架构中,服务被拆分为小型、可独立部署的服务单元,这些服务单元能够通过网络互相通信,形成一个整体的应用系统。然而,随着微服务数量的…...
P1114 “非常男女”计划最优解
原题地址 P1114 “非常男女”计划 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 代码题解 AC代码(1) 因为用的是级的算法,所以最后一个 了,这里使用特判来得到的,给你们放一下代码: #include <bi…...
现在流行的网站开发语言/网站查询进入
Spring Cloud Gateway除了具备请求路由功能之外,也支持对请求的过滤。通过Zuul网关类似,也是通过过滤器的形式来实现的。那么接下来我们一起来研究一下Gateway中的过滤器3.3.1 过滤器基础(1) 过滤器的生命周期Spring Cloud Gatewa…...
广德县建设协会网站/企业模板建站
html中form表示一个表单,用来把一系列的控件包围起来,然后再统一发送这些数据到目标,比如最常见的注册,你说需要填写的资料,都是被封装在form里的,填写完毕后,提交form内的内容,如果不再form内则不会提交而table则是用来布局的 当你填写资料的时候 你有没有发现页面所提供的文本…...
做彩票网站代理/淘宝大数据查询平台
如何玩转iPhone手机?最近手机连接投影仪被大家关注,那么苹果手机是如何连接投影仪的呢?新出的iPhone8可以连接吗?跟随小编一起来了解一下详细的步骤教程吧。苹果手机与投影仪连接的方法教程(步骤) 1、第一次…...
facebook做网站/seo关键词排名价格
第一部分:介绍hadoop集群架构 此部分共分为如下两部分: 第一章:介绍hadoop及其环境介始 欢迎来到管理hadoop的课程。hadoop在处理大数据处于领先地,被称为大数据hadoop。hadoop是一个开源的项目,大约在2005年推出。hadoop已成为…...
男男做爰视频网站/b2b电商平台有哪些
日前,华为已经正式宣布将于6月2日晚上8点举行线上发布会,正式公布可以覆盖手机等移动终端的鸿蒙操作系统,华为EMUI微博正式更名为HarmonyOS。要知道,自从2019年华为官宣鸿蒙操作系统后,鸿蒙系统只是应用到智能电视等终…...
手机网站建设软件有哪些/cps广告联盟
ps:个人学习笔记,视频链接https://www.bilibili.com/video/BV1Y7411d7Ys 参考链接https://blog.csdn.net/bit452/category_10569531.html 文章目录相关知识点线性模型梯度下降1.梯度下降2.随机梯度下降反向传播1.两层神经网络示例2.反向传播计算损失函数对权重偏导P…...