Linux之线程互斥与同步

1.线程互斥相关概念

临界资源:多线程执行流共享的资源就叫做临界资源
临界区:每个线程内部,访问临界自娱的代码,就叫做临界区。
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。
原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。

2.互斥量 

多线程抢票的例子

#include <iostream>
#include <thread>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <cstdio>using namespace std;
int tickets = 10000; // 在并发访问的时候,导致了我们数据不一致的问题!void *getTickets(void *args)
{(void)args;while(true){if(tickets > 0){usleep(1000);printf("%p: %d\n", pthread_self(), tickets);tickets--;}else{break;}}return nullptr;
}int main()
{pthread_t t1,t2,t3;// 多线程抢票的逻辑pthread_create(&t1, nullptr, getTickets, nullptr);pthread_create(&t2, nullptr, getTickets, nullptr);pthread_create(&t3, nullptr, getTickets, nullptr);pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr);
}

 输出结果:

我们发现,票居然被抢到了  -1,甚至有的票没被抢到,有的票被抢了多次,为什么呢?----原因就在于,ticket是全局的共享的,在被多线程并发访问时,由于各个线程对ticket变量进行了修改,出现了数据不一致问题!!!

其实根本原因是因为,对ticket进行 -- 操作并不是原子的,对应了3条汇编指令:

load :将共享变量 ticket 从内存加载到寄存器中
update : 更新寄存器里面的值,执行 -1 操作
store :将新值,从寄存器写回共享变量 ticket 的内存地址

要解决以上问题,需要做到三点:
代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量。 

 

3.对锁的理解

swap或enchange指令

以一条汇编的方式,将内存和CPU寄存器数据进行交换,如果我们在汇编的角度,只有一条汇编语句,我们就认为该汇编语句的执行就是原子的。

在执行流视角,是如何看待CPU上面的寄存器的?

CPU内部的寄存器,本质叫做执行流的上下文,寄存器们的空间是被所有的执行流所共享的,但是寄存器的内容,是被每一个执行流私有的。(上下文)

下边通过例子以图深入理解锁

分以下若干步骤:

 【线程A先执行  movb $0,%al  指令,执行完该指令后,被OS切换】

【线程B执行  movb $0,%al  指令,然后再执行  xchgb  %al,mutex  指令,然后立刻被OS切换】

【线程A执行 xchgb  %al,mutex 指令,由于此时 al 寄存器 的内容=0,因此线程A被挂起等待】

【线程B,由于此时 al 寄存器 的内容=1,因此线程B申请到了锁】

。。。。。。。

【线程B释放锁,此时线程A执行goto lock, 才可以重新申请锁】

4.互斥量的接口

初始化互斥量
初始化互斥量有两种方法:
方法一:静态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

方法二:动态分配 

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t
*restrict attr);
参数:
        mutex:要初始化的互斥量
attr:NULL
销毁互斥量
销毁互斥量需要注意:
使用PTHREAD_ MUTEX_ INITIALIZER初始化的互斥量不需要销毁
不要销毁一个已经加锁的互斥量
已经销毁的互斥量,要确保后面不会有线程再尝试加锁

int pthread_mutex_destroy(pthread_mutex_t *mutex); 

互斥量加锁和解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回-1,错误码errno被设置
调用 pthread_ lock 时,可能会遇到以下情况 :
互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock 调用会陷入阻塞 ( 执行流被挂起 ) ,等待互斥量解锁。

 改进上边的售票系统:

#include <iostream>
#include <thread>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <cstdio>using namespace std;
int tickets = 10000; // 在并发访问的时候,导致了我们数据不一致的问题!//pthread_mutex_t mtx=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mtx;void *getTickets(void *args)
{(void)args;while(true){pthread_mutex_lock(&mtx);if(tickets > 0){usleep(1000);printf("%p: %d\n", pthread_self(), tickets);tickets--;pthread_mutex_unlock(&mtx);}else{pthread_mutex_unlock(&mtx);break;}}return nullptr;
}int main()
{pthread_t t1,t2,t3;pthread_mutex_init(&mtx, NULL);// 多线程抢票的逻辑pthread_create(&t1, nullptr, getTickets, nullptr);pthread_create(&t2, nullptr, getTickets, nullptr);pthread_create(&t3, nullptr, getTickets, nullptr);pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr);pthread_mutex_destroy(&mtx);
}​

 输出结果(部分):

当我们运行代码后发现,运行好多次,也不会出现之前的(有的票没有被卖,有的票被卖了多次,甚至票数到了 -1)这些现象,同时也会发现,此时打印的速度比之前的打印速度慢了不少。原因就在于,当使用了锁以后,线程执行时会串行化!!!

因此,加锁的粒度一定要越小越好!!!

形象化

5.可重入VS线程安全

线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。
重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。 
常见的线程不安全的情况
不保护共享变量的函数
函数状态随着被调用,状态发生变化的函数
返回指向静态变量指针的函数
调用线程不安全函数的函数

 常见的线程安全的情况

每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的;
类或者接口对于线程来说都是原子操作;
多个线程之间的切换不会导致该接口的执行结果存在二义性;

常见不可重入的情况 

调用了 malloc/free 函数,因为 malloc 函数是用全局链表来管理堆的
调用了标准 I/O 库函数,标准 I/O 库的很多实现都以不可重入的方式使用全局数据结构
可重入函数体内使用了静态的数据结构
常见可重入的情况
不使用全局变量或静态变量
不使用用 malloc 或者 new 开辟出的空间
不调用不可重入函数
不返回静态或全局数据,所有数据都有函数的调用者提供
使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据

可重入与线程安全的联系

函数是可重入的,那就是线程安全的
函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题
如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的

可重入与线程安全的区别

可重入函数是线程安全函数的一种。
线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的。

6.死锁

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。
死锁四个必要条件
互斥条件:一个资源每次只能被一个执行流使用
请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系
如何避免死锁
破坏死锁的四个必要条件
加锁顺序一致
避免锁未释放的场景
资源一次性分配

产生死锁(以2个线程为例)  

由于线程A已经申请到了锁1,线程B已经申请到了锁2,但是双方都不让步,这就导致了“死锁”。

 

其实一把锁也能产生死锁,比如申请锁后不释放,还继续申请锁,也产生了“死锁”,如下:

(将释放锁的代码改为申请锁的代码)

7.线程同步

条件变量
当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。
同步概念与竞态条件
同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。
竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解。
条件变量函数 初始化

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

参数:
        cond:要初始化的条件变量
        attr:NULL

销毁

int pthread_cond_destroy(pthread_cond_t *cond) 

等待条件满足

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

参数:
        cond:要在这个条件变量上等待
        mutex:互斥量,后面详细解释

唤醒等待

int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

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

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

相关文章

谷歌查问题

1&#xff0c;打开 it工具箱-里面啥都有 2&#xff0c;找到谷歌 3&#xff0c;访问gpt

E. Yet Another Walking Robot 又一个行走的机器人(map详解代码)

坐标平面上有一个机器人。最初&#xff0c;机器人位于该点&#xff08;0,0&#xff09; .它的路径被描述为字符串s长度n由字符“L”、“R”、“U”、“D”组成。这些字符中的每一个都对应着一些动作&#xff1a; ‘L’&#xff08;左&#xff09;&#xff1a;表示机器人从该点移…

VSCODE自动更新无法连接远程服务器报错“waiting for server log...“的解决方法

问题描述 一觉醒来打开vscode发现连接远程服务器显示无法连接&#xff0c;终端一直报错“waiting for server log…"&#xff0c;经查是因为vscode自动更新到了1.86&#xff0c;对于远程服务器的linux版本要求较高。这里记录下解决方法。 解决方法 1. 下载vscode便携版…

Sketch3D:用于草图到3D生成的样式一致性指南

Sketch3D: Style-Consistent Guidance for Sketch-to-3D Generation Sketch3D&#xff1a;用于草图到3D生成的样式一致性指南 Wangguandong Zheng 重试 错误原因 Southeast UniversityChina 重试 错误原因 wgdzhengseu.edu.cnHaifeng Xia 重试 错误原因 Southeast Universit…

设计模式总结-简单工厂模式

简单工厂模式 创建型模式创建型模式概述创建型模式种类 简单工厂模式模式定义模式动机模式结构模式分析模式实例与解析实例一&#xff1a;简单电视机工厂实例二&#xff1a;权限管理 模式优缺点简单工厂模式的优点简单工厂模式的缺点 模式适用环境模式扩展 小结 创建型模式 创…

如何在Ubuntu系统使用docker部署DbGate容器并发布至公网可访问

文章目录 1. 安装Docker2. 使用Docker拉取DbGate镜像3. 创建并启动DbGate容器4. 本地连接测试5. 公网远程访问本地DbGate容器5.1 内网穿透工具安装5.2 创建远程连接公网地址5.3 使用固定公网地址远程访问 本文主要介绍如何在Linux Ubuntu系统中使用Docker部署DbGate数据库管理工…

Unstructured - 提取非结构化数据

文章目录 一、关于 Unstructured核心概念&#x1f680; Beta 功能&#xff1a;Chipper 模型 二、安装方式一&#xff1a;使用 PYPI方式二&#xff1a;使用源码本地安装安装依赖库测试 三、在Docker运行库添加shell构建自己的 Docker image交互运行 四、PDF文档解析示例 一、关于…

【随笔】Git 高级篇 -- 快速定位分支 ^|~(二十三)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

关于鸿蒙HarmonyOS,现在关注什么可以更高效

对于移动端来讲&#xff0c;今年最火的关键词除了裁员&#xff0c;我想就是鸿蒙HarmonyOS了。其实鸿蒙的推出也给安卓端的同学提供了职业发展的新路径或方向。 鸿蒙&#xff0c;原本源自中国神话传说的名字&#xff0c;如今已成为了科技领域的焦点&#xff0c;招聘网站上也出现…

【C语言】C语言题库【附源码+持续更新】

欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 目录 1、练习2-1 Programming in C is fun! 2、练习2-3 输出倒三角图案 3、练习2-4 温度转换 4、练习2-6 计算物体自由下落的距离 5、练习2-8 计算摄氏温度 6、练习2-9 整数四则运算 7、练习2-10 计算分段函数[1…

ELFK (Filebeat+ELK)日志分析系统

一. 相关介绍 Filebeat&#xff1a;轻量级的开源日志文件数据搜集器。通常在需要采集数据的客户端安装 Filebeat&#xff0c;并指定目录与日志格式&#xff0c;Filebeat 就能快速收集数据&#xff0c;并发送给 logstash 进或是直接发给 Elasticsearch 存储&#xff0c;性能上相…

【计算机毕业设计】网上宠物商店管理系统——后附源码

&#x1f389;**欢迎来到我的技术世界&#xff01;**&#x1f389; &#x1f4d8; 博主小档案&#xff1a; 一名来自世界500强的资深程序媛&#xff0c;毕业于国内知名985高校。 &#x1f527; 技术专长&#xff1a; 在深度学习任务中展现出卓越的能力&#xff0c;包括但不限于…

DSP笔记8-通用GPIO

电源类 AD引脚类 系统相关JTAG 时钟 GPIO (general purpose input output)复用&#xff0c; 复用&#xff0c;I/O引脚&#xff0c;外设的功能引脚&#xff0c; 88个GPIO引脚&#xff0c;通用的输入输出口&#xff0c;功能复用的。 GPIO特点 输入电平与TTL电平兼容。>2.0V…

Redis(Windows版本下载安装和使用)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

使用aspose相关包将excel转成pdf 并导出

SpringBoot 项目 基于aspose相关jar包 将excel 转换成pdf 导出 1、依赖的jar包 &#xff0c; jar获取链接 aspose相关三方jar &#xff0c;下载解压后,在项目路径下建一个libs包&#xff0c;然后将下图两个jar 拷贝至刚新建的libs目录中 2、pom.xml中加入maven引入 <depend…

电脑与多台罗克韦尔AB PLC无线通讯的搭建方法分为几步?

在实际系统中&#xff0c;同一个车间里分布多台PLC&#xff0c;通过上位机集中控制。通常所有设备距离在几十米到上百米不等。在有通讯需求的时候&#xff0c;如果布线的话&#xff0c;工程量较大耽误工期&#xff0c;这种情况下比较适合采用无线通信方式。本方案以组态王和2台…

Logistic 回归为什么适用于二分类问题?

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ Logistic 回归非常适用于二分类问题的主要原因在于它的核心机制和输出特性。首先&#xff0c;Logistic 回归模型基于概率的理念&#xff0c;通过 Sigmoid 函数转换输入特征的线性组合&#xff0c;将任意…

Power Automate custom connector创建示例2-1

一、open power automate custom connector list&#xff1a; Microsoft Power Automate 二、edit custom connector info Authentication type have 4 choice. The example I tested requires Authorization validation, so I chose API Key.

每日一读|经典美文摘抄——等待

等待&#xff0c;是我们和时间的一场博弈。我们凭借着智慧和耐力&#xff0c;与未来做一个交换。 等待的不可知性&#xff0c;是一份考验&#xff0c;一天一天&#xff0c;一步一步走向希望&#xff0c;或者失望。 安娜•卡列尼娜呼喊着&#xff0c;“我是人&#xff0c;我要生…

工业垃圾打包机液压比例阀放大器

液压打包机的种类很多&#xff0c;我们生活中常见的是废纸打包机、金属打包机、纸箱打包机、玉米茎打包机等。然而&#xff0c;牙齿所有的打包机都有他的不同用法。除了我们生活中常见的以外&#xff0c;还有很多其他种类的打包机。让我介绍几件事。一种是捆 绑式打包机设备&am…