【Linux--多线程同步与互斥】

目录

  • 一、线程互斥
    • 1.1相关概念介绍
    • 1.2互斥量mutex
    • 1.3互斥量接口
      • 1.3.1初始化互斥量
      • 1.3.2销毁互斥量
      • 1.3.3互斥量加锁
      • 1.3.4互斥量解锁
      • 1.3.5使用互斥量解决上面分苹果问题
    • 1.4互斥原理
  • 二、可重入与线程安全
    • 2.1相关概念
    • 2.2常见线程不安全的情况
    • 2.3常见不可重入的情况
    • 2.4 可重入与线程安全的关系
  • 三、死锁
  • 四、线程同步
  • 4.1同步概念与竞态条件
  • 4.2条件变量
    • 4.2.1概念
    • 4.2.2接口
      • 4.2.2.1初始化条件变量
      • 4.2.2.2销毁条件变量
      • 4.2.2.2等待条件变量满足
      • 4.2.2.3唤醒等待
      • 4.2.2.5改进分苹果

一、线程互斥

1.1相关概念介绍

  • 临界资源: 多线程执行流共享的资源叫做临界资源
  • 临界区: 每个线程内部访问临界资源的代码,被称为临界区
  • 互斥: 任何时刻,互斥保证有且只有一个执行流进入临界区访问临界资源,通常对临界资源起保护作用
  • 原子性: 不会被任何调度机制打断的操作,该操作只有两态:要么完成,要么未完成
    为什么要有线程互斥?下面模拟下4人分苹果的代码。
    代码:
#include<iostream>
using namespace std;
#include<pthread.h>
#include<string>
#include<vector>
#include<unistd.h>
const int NUM=4;
class ThreadDate
{
public: ThreadDate(string name){_name=name;}string _name;
};
int Apples=100;
void* GetApple(void* args)
{ThreadDate* td=static_cast<ThreadDate*>(args);while(1){if(Apples>0){sleep(1);cout<<td->_name<<"get a apple,apple number"<<--Apples<<endl;}else break;}return nullptr;
}
int main()
{vector<pthread_t> tids;vector<ThreadDate*> tds;for(int i=0;i<NUM;i++){string name="thread"+to_string(i);ThreadDate* td=new ThreadDate(name);tds.push_back(td);pthread_t tid;pthread_create(&tid,nullptr,GetApple,tds[i]);tids.push_back(tid);}for(int i=0;i<NUM;i++){pthread_join(tids[i],nullptr);delete tds[i];}return 0;
}

现象:
在这里插入图片描述
产生该现象的原因:
在这里插入图片描述

1.2互斥量mutex

若线程使用的数据是局部变量,变量的地址空间在线程栈空间内,变量归属单个线程,其他线程无法获得这种变量;但有些变量需要在线程间共享(共享变量),可以通过数据的共享,完成线程之间的交互。多个线程并发的操作共享变量就会带来一些问题

要解决上述分苹果的问题,需要做到三点:

代码必须有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
若多个线程同时要求执行临界区的代码,并且此时临界区没有线程在执行,那么只能允许一个线程进入该临界区
若线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区
这时就需要一把锁,Linux中提供的这把锁被称为互斥量
在这里插入图片描述

1.3互斥量接口

1.3.1初始化互斥量

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

参数:

  • mutex:需要初始化的互斥量的地址
  • attr:初始化互斥量的属性,一般设置为nullptr即可
    返回值:
  • 互斥量初始化成功返回0,失败返回错误码
    使用pthread_mutex_init()函数初始化互斥量的方式被称为动态分配,还可以使用静态分配进行初始化,即下面这种方式:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

1.3.2销毁互斥量

int pthread_mutex_destroy(pthread_mutex_t *mutex);

参数mutex:需要销毁的互斥量的地址

返回值:互斥量销毁成功返回0,失败返回错误码

注意:

  • 使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量不需要销毁
  • 不要销毁一个已经加锁的互斥量
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁

1.3.3互斥量加锁

int pthread_mutex_lock(pthread_mutex_t *mutex);

参数mutex:需要加锁的互斥量的地址

返回值:互斥量加锁成功返回0,失败返回错误码

注意:

  • 互斥量处于未锁状态时,该函数会将互斥量锁定,同时返回成功
  • 发起函数调用时,若其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么线程会在pthread_mutex_lock()函数内部阻塞至互斥量解锁

1.3.4互斥量解锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);

参数mutex:需要解锁的互斥量的地址

返回值:互斥量解锁成功返回0,失败返回错误码

1.3.5使用互斥量解决上面分苹果问题

#include<iostream>
using namespace std;
#include<pthread.h>
#include<string>
#include<vector>
#include<unistd.h>
const int NUM=4;
class ThreadDate
{
public: ThreadDate(string name,pthread_mutex_t* lock){_name=name;_lock=lock;}
public:string _name;pthread_mutex_t* _lock;};
int Apples=100;
void* GetApple(void* args)
{ThreadDate* td=static_cast<ThreadDate*>(args);while(1){pthread_mutex_lock(td->_lock);if(Apples>0){//sleep(1);cout<<td->_name<<"get a apple,apple number"<<Apples--<<endl;pthread_mutex_unlock(td->_lock);}else {pthread_mutex_unlock(td->_lock);break;}}return nullptr;
}
int main()
{pthread_mutex_t lock;pthread_mutex_init(&lock,nullptr);vector<pthread_t> tids;vector<ThreadDate*> tds;for(int i=0;i<NUM;i++){string name="thread"+to_string(i);ThreadDate* td=new ThreadDate(name,&lock);tds.push_back(td);pthread_t tid;pthread_create(&tid,nullptr,GetApple,tds[i]);tids.push_back(tid);}for(int i=0;i<NUM;i++){pthread_join(tids[i],nullptr);delete tds[i];}pthread_mutex_destroy(&lock);return 0;
}

写这个代码的时候出现了一个乌龙。写到这里复盘以下,顺便提一嘴,多线程写代码时考虑的是要多一点。
在这里插入图片描述

1.4互斥原理

引入互斥量后,当一个线程申请到锁进入临界区时,在其他线程看来该线程只有两种状态,要么没有申请锁,要么锁已经释放了,因为只有这两种状态对其他线程才是有意义的。

例如,图中线程1进入临界区后,在线程2、3、4看来,线程1要么没有申请锁,要么线程1已经将锁释放了,因为只有这两种状态对线程2、3、4才是有意义的,当线程2、3、4检测到其他状态(线程1持有锁)时也就被阻塞了。此时对于线程2、3、4而言,线程1的整个操作过程是原子的
在这里插入图片描述
临界区内的线程可能被切换吗?
临界区内的线程是可能进行线程切换。但即便该线程被切走,其他线程也无法进入临界区进行资源访问,因为此时该线程是拿着锁被切走的,锁没有被释放也就意味着其他线程无法申请到锁,也就无法进入临界区进行资源访问了。
互斥锁是否需要被保护?
既然锁是临界资源,那么锁就必须被保护起来,但锁本身就是用来保护临界资源的,那锁又由谁来保护的呢?

锁实际上是自己保护自己的,只需要保证申请锁的过程是原子的,那么锁就是安全的
如何保证申请锁是原子的?
为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用就是把寄存器和内存单元的数据相交换。由于只有一条指令,保证了原子性。
lock和unlock的伪代码:
d6160.png)
6c7540cf84ddb60b3d828201.png)

二、可重入与线程安全

2.1相关概念

  • 线程安全: 多个线程并发同一段代码时,不会出现不同的结果
  • 重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则是不可重入函数。

2.2常见线程不安全的情况

  • 不保护共享变量的函数
  • 函数状态随着被调用,状态发生变化的函数
  • 返回指向静态变量指针的函数
  • 调用线程不安全函数的函数

2.3常见不可重入的情况

  • 调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的
  • 调用了标准I/O库函数,标准I/O可以的很多实现都是以不可重入的方式使用全局数据结构
  • 函数体内使用了静态的数据结构

2.4 可重入与线程安全的关系

  • 线程安全不一定是可重入的,而可重入函数则一定是线程安全的(可重入函数是线程安全函数的一种)
  • 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题
  • 若一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的
  • 若对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数的锁还未释放则会产生死锁,因此是不可重入的

三、死锁

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于的一种永久等待状态
一个锁锁死
在这里插入图片描述
多个锁锁死
线程A申请锁资源的顺序为:锁1、锁2;线程B申请锁资源的顺序为:锁2、锁1

当线程A申请到锁1准备申请锁2时,线程B已申请到锁2准备申请锁1,这时两个线程都会因为申请锁失败而陷入阻塞,并且无法释放锁,进入死锁状态
产生死锁的条件:

  • 互斥条件: 一个资源每次只能被一个执行流使用
  • 请求与保持条件: 一个执行流因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件: 一个执行流已获得的资源,在未使用完之前,不能强行剥夺
  • 循环等待条件: 若干执行流之间形成一种头尾相接的循环等待资源的关系
    避免死锁
  • 破坏死锁的四个必要条件
  • 加锁顺序一致
  • 避免锁未释放的场景
  • 资源一次性分配

四、线程同步

4.1同步概念与竞态条件

同步: 在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,这就叫做同步
竞态条件: 因为时序问题,而导致程序异常,我们称之为竞态条件

  • 单纯的加锁是会存在某些问题的,若某个线程的优先级较高或竞争力较强,每次都能够申请到锁,但申请到锁之后什么也不做,那么这个线程就一直在申请锁和释放锁,这就可能导致其他线程长时间竞争不到锁,引起饥饿问题
  • 单纯的加锁是没有错的,它能够保证在同一时间只有一个线程进入临界区,但它没有高效的让每一个线程使用这份临界资源
  • 现在增加一个规则,当一个线程释放锁后,这个线程不能立马再次申请锁,该线程必须排到这个锁的资源等待队列的最后
  • 增加这个规则之后,下一个获取到锁的资源的线程就一定是在资源等待队列首部的线程,若有十个线程,就能够让这十个线程按照某种次序进行临界资源的访问

4.2条件变量

4.2.1概念

条件变量是利用线程间共享的全局变量进行同步的一种机制,条件变量是用来描述某种资源是否就绪的一种数据化描述

条件变量主要包括两个动作:

  • 一个线程等待条件变量的条件成立而被挂起
  • 另一个线程使条件成立后唤醒等待的线程

条件变量通常需要配合互斥锁一起使用

4.2.2接口

4.2.2.1初始化条件变量

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

参数:

  • cond:需要初始化的条件变量的地址
  • attr:初始化条件变量的属性,一般设置为NULL即可

返回值:条件变量初始化成功返回0,失败返回错误码

使用pthread_cond_init()函数初始化条件的方式被称为动态分配,还可以使用静态分配进行初始化,即下面这种方式:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

4.2.2.2销毁条件变量

int pthread_cond_destroy(pthread_cond_t *cond);

参数cond:需要销毁的条件变量的地址

返回值:条件变量销毁成功返回0,失败返回错误码

注意:使用PTHREAD_COND_INITIALIZER初始化的条件变量不需要销毁

4.2.2.2等待条件变量满足

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

参数:

  • cond:需要等待的条件变量的地址
  • mutex:当前线程所处临界区对应的互斥锁

返回值:函数调用成功返回0,失败返回错误码

4.2.2.3唤醒等待

int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
  • pthread_cond_signal()函数用于唤醒该条件变量等待队列中首个线程
  • pthread_cond_broadcast()函数用于唤醒该条件变量等待队列中的全部线程

参数cond:唤醒在cond条件变量下等待的线程

返回值:函数调用成功返回0,失败返回错误码

4.2.2.5改进分苹果

#include<iostream>
using namespace std;
#include<pthread.h>
#include<string>
#include<vector>
#include<unistd.h>
const int NUM=4;pthread_cond_t cond=PTHREAD_COND_INITIALIZER;class ThreadDate
{
public: ThreadDate(string name,pthread_mutex_t* lock){_name=name;_lock=lock;}
public:string _name;pthread_mutex_t* _lock;};
int Apples=10;
int flag=NUM;
void* GetApple(void* args)
{ThreadDate* td=static_cast<ThreadDate*>(args);while(1){pthread_mutex_lock(td->_lock);pthread_cond_wait(&cond,td->_lock);//线程在等待队列的时候会自动释放锁if(Apples>0){cout<<td->_name<<"get a apple,apple number"<<Apples--<<endl;pthread_mutex_unlock(td->_lock);}else {pthread_mutex_unlock(td->_lock);break;}}cout<<td->_name<<" "<<"quit!"<<endl;pthread_mutex_lock(td->_lock);pthread_cond_wait(&cond,td->_lock);flag--;pthread_mutex_unlock(td->_lock);return nullptr;
}
int main()
{pthread_mutex_t lock;pthread_mutex_init(&lock,nullptr);vector<pthread_t> tids;vector<ThreadDate*> tds;for(int i=0;i<NUM;i++){string name="thread"+to_string(i);ThreadDate* td=new ThreadDate(name,&lock);tds.push_back(td);pthread_t tid;pthread_create(&tid,nullptr,GetApple,tds[i]);tids.push_back(tid);}sleep(3);while(flag){pthread_cond_signal(&cond);sleep(1);}for(int i=0;i<NUM;i++){pthread_join(tids[i],nullptr);delete tds[i];}pthread_mutex_destroy(&lock);pthread_cond_destroy(&cond);return 0;
}

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

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

相关文章

PiflowX组件-JDBCWrite

JDBCWrite组件 组件说明 使用JDBC驱动向任意类型的关系型数据库写入数据。 计算引擎 flink 有界性 Sink: Batch Sink: Streaming Append & Upsert Mode 组件分组 Jdbc 端口 Inport&#xff1a;默认端口 outport&#xff1a;默认端口 组件属性 名称展示名称默…

获奖、买房、出课、维权、购车,我的2023年度总结。

时光如水&#xff0c;岁月如梭。一个典型的小学语文作文的开头。 但是随着年龄的增长&#xff0c;越来越觉得时间过得真的很快啊。转眼间2023年就这么过去了。回看这一年&#xff0c;发现真的做了很多事。 按照惯例&#xff0c;做个总结吧。 获奖 很多人都知道&#xff0c;我去…

百度高级Java面试真题

今年IT寒冬&#xff0c;大厂都裁员或者准备裁员&#xff0c;作为开猿节流主要目标之一&#xff0c;我们更应该时刻保持竞争力。为了抱团取暖&#xff0c;林老师开通了《知识星球》&#xff0c;并邀请我阿里、快手、腾讯等的朋友加入&#xff0c;分享八股文、项目经验、管理经验…

Spring04

一、AOP的概念 AOP 为 (Aspect Oriented Programming) 的缩写&#xff0c;意为&#xff1a;面向切面编程&#xff0c;底层是使用动态代理的技术实现对目标方法的增强和控制访问等功能。 其中AOP中有几个重要的概念: 1、通知:增强的逻辑&#xff0c;或者后期要加入的代码。 2、目…

【js】js解析Token:

一、效果&#xff1a; 二、实现&#xff1a; export function getTokenObject(token) {//通过split()方法将token转为字符串数组,数组中的第二个字符进行解析return token ? JSON.parse(decodeURIComponent(escape(window.atob(token.split(".")[1].replace(/-/g &…

docker Mysql-udf-http

1.Mysql-udf-http镜像已上传到dockerhub中 docker pull heidaodageshiwo/mysql-udf-http:v1 2.启动镜像(默认密码root1234) docker run -tid -p 3306:3306 --namemysql-udf-http --privilegedtrue heidaodageshiwo/mysql-udf-http:v1 3.命令 [rootlocalhost ~]# docker im…

odoo17 | 开发环境设置

前言 开始odoo17开发之前&#xff0c;请先掌握python的基本语法和工具包的使用&#xff0c;以及postgres数据库的安装&#xff0c;和简单的sql使用。以及一些前端的html、css、javascript等前端知识&#xff0c;以及xml、json等数据传输的使用。 本教程同时适用于odoo15-17 …

go语言语法基础

文章目录 前言一、输入和输出常用的字符串格式化符号 二、注释三、Go常用基本语言数据类型数字类型布尔类型字符类型变量与常量数组和切片数组切片 map类型创建map增删改查特别提醒 指针 四、运算符五、条件判断语句if系列switch六、循环语句for循环标准写法死循环while循环do …

Primavera Unifier 项目控制延伸:Phase Gate理论:2/3

阶段Gate的具体内容&#xff1a; 阶段0 根据公司需要和资源现状&#xff0c;决定开展哪些项目。在这个阶段&#xff0c;公司一般需要开展一些脑力风暴或者团队集思广益的活动以获得足够多的点子。一旦团队决定采用某个想法&#xff0c;必须从各个维度去完善它&#xff0c;并使…

【FileZilla的安装与使用(主动与被动模式详解,以及如何利用FileZilla搭建FTP服务器并且进行访问)】

目录 一、FileZilla介绍 1.1 简介 1.2 重要信息和功能 二、FileZilla的安装与使用 2.1 FileZilla服务端安装与配置 2.1.1 安装步骤 2.1.2 新建组 2.1.3 新建用户 2.1.4 新建目录 2.1.5 权限分配 &#xff08;1&#xff09;用户Milk权限分配 &#xff08;2&#xff…

使用 Hyper-V 创建虚拟机

使用 Hyper-V 创建虚拟机 官网教程修改存储目录Hyper-V管理器创建虚拟机启动虚拟机Win10安装教程Press any key to boot from CD or DVD...... 如何使用Windows自带的虚拟机工具来创建虚拟机&#xff0c; 快速创建虚拟机进行学习探讨&#xff0c;如果有环境问题可以立即创建一个…

元旦小礼品:想去面试的时候如何快速打造自己的面试亮点

新年快乐&#xff01; 对于毕业生&#xff0c;或者工作时间不太长的同学来说&#xff0c;面试实习之前写简历是一件很痛苦的事情&#xff0c;特别是简历上没什么亮点&#xff0c;总感觉很心虚。这时候就会发现很多人凡是知道的&#xff0c;叫得上名字的统统写上去。可惜即使通…

VMware之FTP的简介以及搭建使用计算机端口的介绍

&#x1f3ac; 艳艳耶✌️&#xff1a;个人主页 &#x1f525; 个人专栏 &#xff1a;《产品经理如何画泳道图&流程图》 ⛺️ 越努力 &#xff0c;越幸运 目录 一、FTP介绍 1、什么是FTP&#xff1a; 2、FTP适用于以下情况和应用场景&#xff1a; 3、winServer2012搭…

软件测试/测试开发丨Linux 三剑客与管道使用

1、 程序运行环境输入与输出 标准输入 0 read a;echo $a标准输出 1 echo ceshiren.com错误输出 ls not_exist_dir 2、 管道重定向 管道与管道之间可以重定向管道与文件之间可以重定向 echo 11 > /tmp/1 read var </tmp/1错误输出&#xff1a; ls not_exist_dir > /…

ubuntu:beyond compare 4 This license key has been revoked 解决办法

https://www.cnblogs.com/zhibei/p/12095431.html 错误如图所示&#xff1a; 解决办法&#xff1a; &#xff08;1&#xff09;先用find命令找到bcompare所在位置&#xff1a;sudo find /home/ -name *bcompare &#xff08;2&#xff09;进入 /home/whf/.config,删除/bco…

【瞎折腾/3D】无父物体下物体的旋转与移动

目录 说在前面移动World SpaceLocal Space 旋转World SpaceLocal Space 代码 说在前面 测试环境&#xff1a;Microsoft Edge 120.0.2210.91three.js版本&#xff1a;0.160.0其他&#xff1a;本篇文章中只探讨了无父对象下的移动与旋转&#xff0c;有父对象的情况将在下篇文章中…

springboot 接收appsflyer 参数

1、官网登陆&#xff1a;hq1.appsflyer.com a、配置接收请求的地址和需要的事件 配置详情 2、Controller配置接收 RequestMapping(value "/req", method POST)ResponseBodypublic ResData req(RequestBody Map<String, String> map) {String jsonObject J…

一文搞懂什么是缓存穿透、缓存雪崩、缓存击穿三个概念,以及解决方案

先理解概念&#xff1a;【注&#xff1a;我们这里说的是分布式、高并发环境】 一、缓存穿透是什么&#xff1f; 缓存穿透是指&#xff1a;请求【可以有很多】的数据在缓存、关系型数据库中都不存在&#xff0c;每次来查询都会查询到关系型数据库中。 解决方案&#xff1a; 1、将…

CUMT--Java复习--核心类

目录 一、装箱与拆箱 二、“”与equals 三、字符串类 1、String、StringBuffer、StringBuilder的区别 2、String类 3、StringBuffer类 4、StringBuilder类 四、类与类之间关系 一、装箱与拆箱 基本类型与对应封装类之间能够自动进行转换&#xff0c;本质就是Java的自…

强烈推荐 25个 前端开源中后台管理系统

作为程序员&#xff0c;构建一套个人专属的后台管理系统非常重要。这不仅是为了打造自己独有的开发生态&#xff0c;更是因为我们正处于个人开发和AI模型泛滥的时代。利用AI增强自己的系统变得尤为关键。然而&#xff0c;在UI界面设计方面&#xff0c;我们可能需要参考开源项目…