当前位置: 首页 > news >正文

【C语言】多线程之条件竞争

多线程(三)

  • 条件竞争
    • 并发程序引起的共享内存的问题
      • 死锁
    • 互斥锁机制
      • 生产者消费者模型
    • 信号量机制
  • 解决:

条件竞争

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
void* Print(char* str){printf("%s ",str);
}int main(){pthread_t thread1,thread2;pthread_create(&thread1,NULL,(void*)&Print,"Hello");pthread_create(&thread2,NULL,(void*)&Print,"World");return 0;
}

编译运行后,发现没有预期输出。

原因:主线程的退出会导致创建的线程退出

使用pthread_create函数创建两个线程,两个线程创建后,并不影响主线程的执行,所以这里就存在三个线程的竞争关系。主线程执行return 0;先于另外两个线程的打印函数。所以看不见另外两个线程的输出。为了使return 0;语句在另外两个进程后执行,可以采用sleep()函数进行延迟,就可以得到输出了,这就是条件竞争。

在遇到条件竞争的问题中,采用sleep()函数进行延迟似乎也能解决问题。实则不然,弊端很明显:

  1. 不能判断延迟的时间长度。
  2. 使程序执行卡顿。

最适当的解决方法是采用锁机制。

并发程序引起的共享内存的问题

有两个进程,两个进程共享全局变量s。两个进程都执行一个计数功能的函数,直观地看过去,th1运行时s++要执行10000次,th2运行时s++也要执行10000次,似乎计算得到的最后s应该是20000。但实际上是这样的吗?

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
int s = 0;
void *func(void*args){int i;for(i = 0; i < 10000; i++){s++;}return NULL;
}
int main(){pthread_t th1;pthread_t th2;pthread_create(&th1, NULL, func, NULL);pthread_create(&th2, NULL, func, NULL);pthread_join(th1, NULL);pthread_join(th2, NULL);printf("%s = %d\n", s);return 0;
}

编译运行后,发现输出并不是20000,而是12657。

运行了3次这个程序,每次的结果都不同。 用这个演示来表示一下,多线程之间是资源共享的。

s++ 是有三个步的,读取s,s+1,写入s。

在程序运行的某个时刻,th1携带myfunc执行s++,读取s,此时s=100,进行s+1, 与此同时th2也开始读取s,此时的s还是等于100, 这时th1,执行写入s=101,th2执行s++,写入s ,s=101. th2中的s 就会覆盖掉 th1中的s 。这样造成了结果的误差。

原因:当我们执行s++,底层发生的事件其实是:内存中读取s→将s+1→将s写入到内存。这不是一个原子化操作,当两个线程交错运行的时候,很容易发生结果的丢失。因此最后的结果肯定是要小于20000的。这种情况有种专有名词,叫race condition。

为了解决这个问题,我们可以加锁。

#include <pthread.h>
int s = 0;
pthread_mutex_t lock;	//锁的声明
void *func(void *args){int i;for(i = 0; i < 10000; i++){	//给临界区代码加锁实现原子化操作pthread_mutex_lock(&lock);s++;pthread_mutex_unlock(&lock);}return NULL;
}
int main(){pthread_t th1;pthread_t th2;//锁初始化pthread_mutex_init(&lock, NULL);pthread_create(&th1, NULL, func, NULL);pthread_createe(&th2, NULL, func, NULL);pthread_join(th1, NULL);pthread_join(th2, NULL);printf("s = %d\n, s);return 0;
}

改进后的代码如下,学过操作系统会很好理解,无非就是为了保证共享内存区(临界区)的原子化操作,我们可以在进这段代码之前加锁(pthread_mutex_lock),意味着其他线程看到这段内存被其他人占有的时候,就不去抢占,等这段内存被解锁(pthread_mutex_unlock)之后,它才有读写这段临界区的权利。

但其实这种方式的执行速度并不快,比如这段代码里,每个线程都要进行10000次加解锁的操作,它能解决内存读写冲突的问题,但是却牺牲了效率。
锁的作用是什么呢?

前面说过多线程是并发执行的,th1运行后 进行了加锁,th2这时候想要运行,就必须等待th1解锁之后才行。
(一个卫生间,多个人要用,第一个人进去之后,把门锁上了,后边的人就得排队等着,第一个方便完了,解锁开门出来,第二个人进去,继续锁门……)

锁 在提高程序的安全性的同时,也降低了程序的效率。

锁的使用方法

pthread_mutex_t lock; 声明一个锁

pthread_mutex_init(&lock,NULL); 对声明的锁进行初始化

pthread_mutex_lock(&lock); //上锁 此时其他线程就开始等待

pthread_mutex_lock(&unlock); //解锁 其他线程可以使用资源了

死锁

拿上边举例,th1运行后,th2会等待th1解锁,才能运行,如果程序出现错误中断了,th1没有执行完,重新启动后,th1又重新执行,这时候th2排在th1前边,需要等待th1的解锁 ,而th1又在等待th2结束。 这样就造成了相互等待的情况,这个就是死锁。

互斥锁机制

通过访问时对共享资源加锁的方法,防止多个线程同时访问共享资源。锁有两种状态:未上锁和已上锁。在访问共享资源时,进行上锁,在访问结束后,进行解锁。若在访问时,共享资源已被其它线程锁住了,则进入堵塞状态等待该线程释放锁再继续下一步的执行。这种锁我们称为互斥锁。

互斥锁相关函数介绍:

1、pthread_mutex_init :初始化一个互斥锁。

函数原型:int pthread_mutex_init(pthread_mutex_tmutex,constpthread_mutexattr_tattr);

2、pthread_mutex_lock:若所访问的资源未上锁,则进行lock,否则进入堵塞状态。

函数原型:intpthread_mutex_lock(pthread_mutex_t*mutex);

3、pthread_mutex_unlock:对互斥锁进行解锁。

函数原型:intpthread_mutex_unlock(pthread_mutex_t*mutex);

4、pthread_mutex_destroy:销毁一个互斥锁。

函数原型:intpthread_mutex_destroy(pthread_mutex_t*mutex);

生产者消费者模型

生产者和消费者在同一时间段内共用同一个存储空间,生产者往存储空间中生成产品,消费者从存储空间中取走产品。当存储空间为空时,消费者阻塞;当存储空间满时,生产者阻塞。(下面代码中存储空间为1)

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>int buf = 0;pthread_mutex_t mut;
void producer(){while(1){pthread_mutex_lock(&mut);if(buf == 0){buf = 1;printf("produced an item.\n");sleep(1);}pthread_mutex_unlock(&mut);}
}
void consumer(){while(1){pthread_mutex_lock(&mut);if(buf == 1){buf = 0;printf("consumed an item.\n");sleep(1);}pthread_mutex_unlock(&mut);}
}int main(void){pthread_t thread1,thread2;pthread_mutex_init(&mut,NULL);pthread_create(&thread1,NULL,&producer,NULL);consumer(&buf);pthread_mutex_destroy(&mut);return 0;
}

从执行结果可以看出,运行顺序井然有序。生产后必是消费,消费完后必是生产。由于互斥锁机制的存在,生产者和消费者不会同时对共享资源进行访问。

信号量机制

上面了解到的互斥锁有两种状态:资源为0和1的状态。当我们所拥有的资源大于1时,可以采用信号量机制。在信号量机制中,我们有n个资源(n>0)。在访问资源时,若n>=1,则可以访问,同时信号量-1,否则堵塞等待直到n>=1。其实互斥锁可以看出信号量的一种特殊情况(n=1)。

信号量相关函数的介绍:
头文件:semaphore.h
1、sem_init函数:初始化一个信号量。
函数原型:int sem_init(sem_t* sem, int pshared, unsigned int value);
参数:sem:指定了要初始化的信号量的地址;pshared:如果其值为0,就表示信号量是当前进程的局部信号量,否则信号量就可以在多个进程间共享;value:指定了信号量的初始值;返回值:成功=>0 , 失败=> -12、 sem_post函数:信号量的值加1,如果加1后值大于0:等待信号量的值变为大于0的进程或线程被唤醒。
函数原型:int sem_post(sem_t* sem);
返回值:成功=>0 , 失败=> -13、sem_wait函数:信号量的减1操作。如果当前信号量的值大于0,则可继续执行。如果当前信号量的值等于0,则会堵塞,直到信号量的值大于0.
函数原型:int sem_wait(sem_t* sem);
返回值:成功=>0 , 失败=> -14、sem_destroy函数:销毁一个信号量。
函数原型:int sem_destroy(sem_t* sem);
返回值:成功=>0 , 失败=> -15、sem_getvalue函数:获取信号量中的值。
函数原型:int sem_getvalue(sem_t* sem, int* sval);
获取信号量的值,并放在&sval上。
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
#include<unistd.h>sem_t npro; //还可以生产多少
sem_t ncon; //还可以消费多少void* producer(void* arg){while(1){int num;sem_wait(&npro); //先判断是否可以生产sem_post(&ncon); //生产一个,可消费数+1sem_getvalue(&ncon,&num);printf("produce one,now have %d items.\n",num);sleep(0.7);}
}void consumer(void* arg){while(1){int num;sem_wait(&ncon); //判断是否可以消费sem_post(&npro); //消费一个,可生产数+1sem_getvalue(&ncon,&num);printf("consume one,now have %d items.\n",num);sleep(1);}
}int main(void){pthread_t thread1,thread2;//init semaphoresem_init(&npro,0,5); //设最大容量为5sem_init(&ncon,0,0);pthread_create(&thread1,NULL,&producer,NULL);consumer(NULL);return 0;
}

同样也可以解决条件竞争问题,而且使用范围更广了。

解决:

  • pthread_join()函数
  • 互斥锁机制
  • 信号量机制

相关文章:

【C语言】多线程之条件竞争

多线程&#xff08;三&#xff09;条件竞争并发程序引起的共享内存的问题死锁互斥锁机制生产者消费者模型信号量机制解决&#xff1a;条件竞争 #include<stdio.h> #include<stdlib.h> #include<pthread.h> void* Print(char* str){printf("%s ",s…...

UE NavigationSystem的相关实现

导航数据的构建流程导航数据的收集导航系统中绑定了Actor、Component注册完成以及取消时的委托&#xff0c;通过这些委托把数据及时更新到导航系统的八叉树结构中导航系统的辅助结构DefaultOctreeController、DefaultDirtyAreasController分别承担了空间数据查询和置脏区域重新…...

Java 继承

文章目录1. 继承概述2. 变量的访问特点3. super 关键字4. 构造方法的访问特点5. 成员方法的访问特点6. 方法重写7. 继承案例1. 继承概述 继承是面向对象三大特征之一。可以使得子类具有父类的属性和方法&#xff0c;还可以在子类中重新定义&#xff0c;追加属性和方法。 publ…...

Python学习笔记8:异常

异常 一些内置的异常类 类名描述Exception几乎所有的异常类都是从它派生而来的AttributeError引用属性或给它赋值失败时引发OSError操作系统不能执行指定的任务&#xff08;如打开文件&#xff09;时引发&#xff0c;有多个子类IndexError使用序列中不存在的索引时引发&#…...

python保留小数函数总结

python保留小数——‘%f’‘%.nf’% x&#xff08;定义的变量&#xff09; 例子&#xff1a;a 82.16332 print(%.1f% a) print(%.2f% a) print(%.3f% a) print(%.4f% a) print(%.10f% a)输出结果python保留小数——format&#xff08;&#xff09;函数Python2.6 开始&#xff…...

狐狸优化算法(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密…...

浏览器自动化框架沦为攻击者的工具

5月27日消息&#xff0c;安全公司Team Cymru的研究人员表示&#xff0c;越来越多的威胁参与者正在使用免费的浏览器自动化框架作为其攻击活动的一部分。 研究人员表示&#xff0c;该框架的技术准入门槛故意保持在较低水平&#xff0c;以创建一个由内容开发者和贡献者组成的活跃…...

SQL必备知识(自用)

数据库基础知识sql和mysql的区别&#xff1a;数据库查询大全&#xff08;select&#xff09;1、select 字段名 from 表&#xff1b;2、In查询&#xff1a;用于过滤你所需要查询的内容3、范围查询&#xff1a;between4、模糊查询&#xff1a;like5、查询空值/非空&#xff1a;is…...

BI工具术语表大全:从字母A-Z全面收录

谈到商业智能行业&#xff0c;变革是不可避免的。为了跟上步伐&#xff0c;各种各样的BI 解决方案正在快速迭代更新&#xff0c;以满足企业的数字化需求&#xff0c;那么市场上BI 工具种类繁杂&#xff0c;到底如何选择适合功能全面、满足自己企业运转情况的、合适的BI 工具呢&…...

vue3 + vite + ts 集成mars3d

vue3 vite ts 集成mars3d 文章目录vue3 vite ts 集成mars3d前言一、创建一个vue3 vite ts项目二、引入mars3d相关依赖三、vite.config.ts 相关配置四、 新建DIV容器 创建地图前言 使用mars3d过程中&#xff0c;需要集成mars3d到自己的项目中&#xff0c;mars3d开发教程…...

跨境卖家必看的沃尔玛Walmart商家入驻教程

沃尔玛Walmart作为作为全球连锁超市的鼻祖&#xff0c;有不可比拟的知名度。当沃尔玛从线下延伸到线上后&#xff0c;就成为一个自带IP与流量的线上平台&#xff0c;在全世界都拥有数量庞大的消费者群体。所以龙哥就结合自己注册Walmart的过程给大家详细讲解一下。 Walmart卖家…...

【GANs】什么是饱和损失函数 Non-Saturating LossFunction

Saturating VS Non-Saturating Loss functions in GANs【GANs】什么是饱和损失函数 Non-Saturating LossFunctionSaturating VS Non-Saturating Loss functions in GANs 饱和Loss 普通GAN loss是生成器希望最小化被判断为假的概率。x取值范围是[0,1]&#xff0c;所以图中函数…...

USB接口虚拟网卡

1 基本概念 1.1 USB转以太网 - ASIX 4-byte length header before every ethernet packet. - Microchip LAN7800 128x32 bit Descriptor RAM, 32 bits DP_DATA address offset 030h for Descriptor RAM access. - Windows CMD参数格式&#xff1a; route /? -> Linux -h …...

基于SpringBoot的外卖项目的优化

基于SpringBoot的外卖项目的优化1、缓存优化1.1、缓存短信验证码问题分析代码改造1.2、缓存菜品数据实现思路1.3、Spring Cache介绍常用注解CachePutCacheEvictCacheable使用方式1.4、缓存套餐数据实现思路代码改造2、读写分离2.1、主从复制存在的问题介绍配置配置主库--master…...

Ubuntu20.04/22.04 ESP32 命令行开发环境配置

ESP32 芯片系列 ESP32分三个系列 ESP32-S ESP32-S3: Xtensa 32位 LX7 双核 240 MHz, 384KB ROM, 512KB SRAM, QFN7x7, 56-pin, 2.4G Wi-Fi BTESP32-S2: Xtensa 32位 LX7 单核 240 MHz, 128KB ROM, 320KB SRAM, QFN7x7, 56-pin, 2.4G Wi-Fi ESP32-C ESP32-C3: RISC-V 32位 单…...

Kali Linux使用Metasploit生成木马入侵安卓系统

额&#xff0c;这是我最后一篇文章了&#xff0c;周一我们开学了 文章目录前言一、Metasploit是什么&#xff1f;演示环境二、生成可执行木马文件1.生成2.运行命令并生成木马配置参数入侵安卓手机命令1.查看对方手机系统信息查看对方手机安装哪些app文件总结前言 前言&#xf…...

数据库复习1

一. 简答题&#xff08;共1题&#xff0c;100分&#xff09; 1. (简答题) 存在数据库test&#xff0c;数据库中有如下表&#xff1a; 1.学生表 Student(Sno,Sname,Sage,Ssex) --Sno 学号,Sname 学生姓名,Sage 出生年月,Ssex 学生性别 主键Sno 2.教师表 Teacher(Tno,Tname) --T…...

18. linux系统基础

shell 命令解析器 命令解析器作用&#xff1a; 他把在终端上输出的命令 给你解析成内核可以识别的指令&#xff0c;内核 是经过命令解析器的加工 shell在找命令的时候&#xff0c;所包含的路径&#xff0c;就是在这些路径里去 找 找到就执行 找不到就报错 报错 要么 这个命…...

ssh远程登录报错:kex_exchange_identification: Connection closed by remote host

基本信息系统&#xff1a;MacOS Catalina 10.15.7报错信息&#xff1a;终端登录远程服务器时报错&#xff1a;kex_exchange_identification: Connection closed by remote host复制然而服务商的一键登录或VNC登录正常。解决方案首先使用以下命令debug登录过程&#xff0c;以便定…...

Quartus II 的入门级使用

好久没有用VHDL写东西了&#xff0c;今天需要完成一个项目&#xff0c;重新复习一下新建工程新建工程file-->New Project Wizard, next, 选择存放的路径名字&#xff08;projecttop-level 名字要相同&#xff09;&#xff0c;next&#xff0c;File name名字同上&#xff0c;…...

网络编程(Modbus进阶)

思维导图 Modbus RTU&#xff08;先学一点理论&#xff09; 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议&#xff0c;由 Modicon 公司&#xff08;现施耐德电气&#xff09;于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器

——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的​​一体化测试平台​​&#xff0c;覆盖应用全生命周期测试需求&#xff0c;主要提供五大核心能力&#xff1a; ​​测试类型​​​​检测目标​​​​关键指标​​功能体验基…...

Keil 中设置 STM32 Flash 和 RAM 地址详解

文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错

出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上&#xff0c;所以报错&#xff0c;到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本&#xff0c;cu、torch、cp 的版本一定要对…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

CSS设置元素的宽度根据其内容自动调整

width: fit-content 是 CSS 中的一个属性值&#xff0c;用于设置元素的宽度根据其内容自动调整&#xff0c;确保宽度刚好容纳内容而不会超出。 效果对比 默认情况&#xff08;width: auto&#xff09;&#xff1a; 块级元素&#xff08;如 <div>&#xff09;会占满父容器…...

管理学院权限管理系统开发总结

文章目录 &#x1f393; 管理学院权限管理系统开发总结 - 现代化Web应用实践之路&#x1f4dd; 项目概述&#x1f3d7;️ 技术架构设计后端技术栈前端技术栈 &#x1f4a1; 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 &#x1f5c4;️ 数据库设…...

在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案

这个问题我看其他博主也写了&#xff0c;要么要会员、要么写的乱七八糟。这里我整理一下&#xff0c;把问题说清楚并且给出代码&#xff0c;拿去用就行&#xff0c;照着葫芦画瓢。 问题 在继承QWebEngineView后&#xff0c;重写mousePressEvent或event函数无法捕获鼠标按下事…...

零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程

STM32F1 本教程使用零知标准板&#xff08;STM32F103RBT6&#xff09;通过I2C驱动ICM20948九轴传感器&#xff0c;实现姿态解算&#xff0c;并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化&#xff0c;适合嵌入式及物联网开发者。在基础驱动上新增…...

深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏

一、引言 在深度学习中&#xff0c;我们训练出的神经网络往往非常庞大&#xff08;比如像 ResNet、YOLOv8、Vision Transformer&#xff09;&#xff0c;虽然精度很高&#xff0c;但“太重”了&#xff0c;运行起来很慢&#xff0c;占用内存大&#xff0c;不适合部署到手机、摄…...