C语言中的信号量、进程同步与互斥、线程同步与互斥详解

news/2024/9/17 20:51:05/ 标签: c语言, 开发语言, 进程管理

文章目录

      • 信号量的基本概念
      • 信号量的基本操作
      • 信号量的使用场景
      • C语言中使用信号量
        • 一,信号量相关的函数
        • 示例代码
        • 解释:
      • 二、进程同步与互斥
        • 1. 进程同步的方式:信号量
        • 示例代码:使用信号量进行进程同步
        • 解释:
        • 2. 进程互斥的方式:文件锁
        • 示例代码:文件锁实现进程互斥
        • 解释:
      • 三、线程同步与互斥
        • 1. 线程同步:条件变量
        • 示例代码:使用条件变量进行线程同步
        • 解释:
        • 2. 线程互斥:互斥锁(Mutex)
        • 示例代码:使用互斥锁实现线程互斥
        • 解释:
      • 四、总结


在C语言中,信号量是一种用于多线程或进程同步的机制,主要用于解决并发问题,确保多个线程或进程对共享资源的正确访问。它在多进程和多线程编程中扮演重要角色,能有效防止竞态条件和数据不一致。此外,多线程和多进程编程中的同步与互斥问题也是并发编程中必须面对的核心挑战。本文将详细介绍信号量及其应用,并展示如何通过信号量、互斥锁、条件变量等机制进行线程和进程间的同步与互斥。

信号量的基本概念

信号量可以看作是一个计数器,其值表示可用资源的数量。它有两种基本类型:

  1. 二元信号量(Binary Semaphore):也叫作互斥锁(mutex),只有两个状态:0 或 1。它用来确保某一时刻只有一个线程或进程能够访问某个资源。
  2. 计数信号量(Counting Semaphore):信号量的值可以是大于1的数字,表示可以有多个线程或进程同时访问某个资源。

信号量的基本操作

信号量有两个基本操作:

  1. P操作(等待操作,wait/down)

    • 如果信号量的值大于0,执行P操作后信号量的值减1,表示一个线程获取了资源。
    • 如果信号量的值等于0,调用线程将被阻塞,直到信号量的值大于0为止。
  2. V操作(释放操作,signal/up)

    • 执行V操作后,信号量的值加1,表示释放了一个资源。如果有其他线程因为信号量值为0而被阻塞,那么此时会唤醒一个等待的线程。

信号量的使用场景

信号量可以用于解决多个线程或进程之间的同步问题,常见的场景包括:

  • 互斥锁(Mutex):确保某一时刻只有一个线程访问某个资源,可以用二元信号量来实现。
  • 生产者-消费者问题:生产者生产数据,消费者消费数据,信号量可以用来确保生产者不会过度生产,消费者不会过度消费。
  • 限制并发数:计数信号量可以用来限制对某些资源的并发访问数量,比如限制线程池中的线程数量。

C语言中使用信号量

在C语言中,信号量的实现通常依赖于POSIX标准中的<semaphore.h>库。

一,信号量相关的函数
  1. sem_init():初始化信号量。

    int sem_init(sem_t *sem, int pshared, unsigned int value);
    
    • sem: 指向信号量的指针。
    • pshared: 如果为 0,信号量用于线程之间同步;如果为非0,信号量可用于进程间同步。
    • value: 信号量的初始值。
  2. sem_wait():P操作,等待信号量减1。如果信号量为0,则阻塞。

    int sem_wait(sem_t *sem);
    
  3. sem_post():V操作,释放信号量,即增加信号量的值。

    int sem_post(sem_t *sem);
    
  4. sem_destroy():销毁信号量。

    int sem_destroy(sem_t *sem);
    
示例代码

以下是使用信号量实现生产者-消费者问题的简单示例:

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>#define BUFFER_SIZE 5int buffer[BUFFER_SIZE];
int count = 0;sem_t empty; // 空位信号量
sem_t full;  // 已满信号量
pthread_mutex_t mutex; // 互斥锁,保护缓冲区void *producer(void *arg) {int item;for (int i = 0; i < 10; i++) {item = i;sem_wait(&empty); // 等待空位pthread_mutex_lock(&mutex); // 进入临界区buffer[count++] = item;printf("Producer produced: %d\n", item);pthread_mutex_unlock(&mutex); // 退出临界区sem_post(&full); // 增加满位sleep(1);}
}void *consumer(void *arg) {int item;for (int i = 0; i < 10; i++) {sem_wait(&full); // 等待满位pthread_mutex_lock(&mutex); // 进入临界区item = buffer[--count];printf("Consumer consumed: %d\n", item);pthread_mutex_unlock(&mutex); // 退出临界区sem_post(&empty); // 增加空位sleep(1);}
}int main() {pthread_t prod, cons;sem_init(&empty, 0, BUFFER_SIZE); // 初始化空位信号量,初值为缓冲区大小sem_init(&full, 0, 0);            // 初始化满位信号量,初值为0pthread_mutex_init(&mutex, NULL); // 初始化互斥锁pthread_create(&prod, NULL, producer, NULL);pthread_create(&cons, NULL, consumer, NULL);pthread_join(prod, NULL);pthread_join(cons, NULL);sem_destroy(&empty); // 销毁信号量sem_destroy(&full);  // 销毁信号量pthread_mutex_destroy(&mutex); // 销毁互斥锁return 0;
}
解释:
  1. empty 信号量:用来表示缓冲区中空位的数量,初始值为缓冲区的大小。
  2. full 信号量:用来表示缓冲区中已经存储的项目的数量,初始值为0。
  3. mutex:互斥锁,用来确保对缓冲区的访问是线程安全的。
  4. 生产者线程:生产者会等待有空位(sem_wait(&empty)),然后放入数据,并增加已满的信号量(sem_post(&full))。
  5. 消费者线程:消费者会等待缓冲区有数据(sem_wait(&full)),然后取出数据,并增加空位的信号量(sem_post(&empty))。

在扩展信号量的基础上,我们还可以探讨多线程和多进程的同步与互斥问题。它们在并发编程中非常重要。我们将深入分析如何通过不同机制实现同步与互斥,包括线程和进程的同步与互斥。

二、进程同步与互斥

在多进程编程中,多个进程可以并发执行,但它们往往需要访问共享资源(如共享内存、文件等)。为了避免竞态条件(Race Condition),我们需要某种机制来同步进程和控制对共享资源的访问。常见的同步与互斥机制包括信号量、共享内存与管道、文件锁等。

1. 进程同步的方式:信号量

进程间的同步可以使用信号量。POSIX信号量提供了一种适用于进程之间同步的方法。

示例代码:使用信号量进行进程同步

以下是使用信号量实现两个进程间同步的简单示例:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>sem_t sem;void processA() {printf("Process A: Running...\n");sleep(1);printf("Process A: Done, signaling Process B\n");sem_post(&sem);  // 释放信号量,通知B可以运行
}void processB() {printf("Process B: Waiting for signal from A...\n");sem_wait(&sem);  // 等待A的信号printf("Process B: Received signal, Running...\n");
}int main() {sem_init(&sem, 1, 0);  // 初始化信号量,1表示用于进程间同步,初始值为0pid_t pid = fork();  // 创建子进程if (pid < 0) {perror("fork failed");exit(EXIT_FAILURE);} else if (pid == 0) {processB();  // 子进程执行B} else {processA();  // 父进程执行Await(NULL);  // 等待子进程结束}sem_destroy(&sem);  // 销毁信号量return 0;
}
解释:
  1. sem_init(&sem, 1, 0):初始化信号量,pshared为1,表示信号量在进程间共享,初值为0表示B进程一开始被阻塞。
  2. 父进程(A):完成任务后,通过sem_post向B进程发出信号,B进程才能继续。
  3. 子进程(B):使用sem_wait等待信号,接到A的信号后继续执行。
2. 进程互斥的方式:文件锁

进程之间可以通过文件锁来实现互斥访问共享资源。POSIX提供了flock函数来锁定文件,从而实现互斥。

示例代码:文件锁实现进程互斥
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/file.h>void write_to_file(const char *filename) {int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0644);if (fd == -1) {perror("open failed");exit(EXIT_FAILURE);}if (flock(fd, LOCK_EX) == -1) {  // 独占锁perror("flock failed");exit(EXIT_FAILURE);}printf("Writing to file...\n");dprintf(fd, "Process %d writing\n", getpid());sleep(2);  // 模拟长时间操作printf("Done writing\n");flock(fd, LOCK_UN);  // 解锁close(fd);
}int main() {pid_t pid = fork();if (pid == 0) {write_to_file("output.txt");  // 子进程写文件} else {write_to_file("output.txt");  // 父进程写文件wait(NULL);  // 等待子进程结束}return 0;
}
解释:
  1. 文件锁(flock:通过独占锁(LOCK_EX)来确保只有一个进程在写文件,其他进程被阻塞,避免写操作的冲突。

三、线程同步与互斥

多线程编程中,线程共享进程的内存空间,因此更容易发生竞态条件。我们可以使用互斥锁(Mutex)、条件变量(Condition Variable)、读写锁(Read-Write Lock)等机制来同步线程之间的操作。

1. 线程同步:条件变量

条件变量允许线程在某些条件未满足时进入等待状态,并由其他线程通知条件已经满足,从而继续执行。

示例代码:使用条件变量进行线程同步
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>pthread_mutex_t mutex;
pthread_cond_t cond;
int ready = 0;void *worker(void *arg) {pthread_mutex_lock(&mutex);while (!ready) {pthread_cond_wait(&cond, &mutex);  // 等待条件满足}printf("Worker: Condition met, working...\n");pthread_mutex_unlock(&mutex);return NULL;
}void *setter(void *arg) {sleep(2);pthread_mutex_lock(&mutex);ready = 1;printf("Setter: Condition met, notifying worker...\n");pthread_cond_signal(&cond);  // 发出条件信号pthread_mutex_unlock(&mutex);return NULL;
}int main() {pthread_t thread1, thread2;pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond, NULL);pthread_create(&thread1, NULL, worker, NULL);pthread_create(&thread2, NULL, setter, NULL);pthread_join(thread1, NULL);pthread_join(thread2, NULL);pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;
}
解释:
  1. 条件变量(pthread_cond_wait:等待条件变量,释放互斥锁并等待条件满足。
  2. 信号通知(pthread_cond_signal:条件满足后,setter线程通过pthread_cond_signal通知worker线程继续执行。
2. 线程互斥:互斥锁(Mutex)

互斥锁是确保同一时刻只有一个线程能够进入临界区的常用方法。

示例代码:使用互斥锁实现线程互斥
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>pthread_mutex_t mutex;
int counter = 0;void *increment(void *arg) {for (int i = 0; i < 10; i++) {pthread_mutex_lock(&mutex);  // 进入临界区counter++;printf("Thread %d incremented counter to %d\n", *(int *)arg, counter);pthread_mutex_unlock(&mutex);  // 退出临界区sleep(1);}return NULL;
}int main() {pthread_t thread1, thread2;int id1 = 1, id2 = 2;pthread_mutex_init(&mutex, NULL);pthread_create(&thread1, NULL, increment, &id1);pthread_create(&thread2, NULL, increment, &id2);pthread_join(thread1, NULL);pthread_join(thread2, NULL);pthread_mutex_destroy(&mutex);return 0;
}
解释:
  1. 互斥锁(pthread_mutex_lock:确保同一时刻只有一个线程能修改共享变量counter
  2. 锁与解锁:通过pthread_mutex_unlock解锁,其他线程才能进入临界区。

四、总结

  1. 信号量:用于控制对资源的访问,适用于线程或进程间同步。
  2. 互斥锁:用于确保临界区中的操作是原子的,防止多个线程同时修改共享资源。
  3. 条件变量:用于线程间的高级同步机制,线程可以等待某个条件成立并由其他线程通知。
  4. 进程间同步:可以使用POSIX信号量、文件锁等机制。
  5. 线程间同步:可以使用互斥锁、条件变量等机制。

通过这些工具,可以确保多线程和多进程程序中对共享资源的正确访问,避免竞态条件和数据不一致的发生。


http://www.ppmy.cn/news/1525012.html

相关文章

云计算实训43——部署k8s基础环境、配置内核模块、基本组件安装

一、前期系统环境准备 1、关闭防火墙与selinux [rootk8s-master ~]# systemctl stop firewalld[rootk8s-master ~]# systemctl disable firewalldRemoved symlink /etc/systemd/system/multi-user.target.wants/firewalld.service. Removed symlink /etc/systemd/system/dbus…

Mysql梳理3——基本的SELECT语句

3.1 SELECT SELECT 1; #没有任何子句 SELECT 9/2 #没有任何子句 3.2 SELECT...FROM 语法: SELECT 标识选择哪些列 FROM 标识从哪个表选择 选择全部列&#xff1a; SELECT * FROM departments; 注意&#xff1a;一般情况下&#xff0c;除非需要使用表中所有的字…

新能源汽车安全问题如何解决?细看“保护罩”连接器的守护使命

「当前市场上绝大部分电池的安全系数远远不够」。 在一场世界动力电池大会上&#xff0c;宁德时代的董事长曾毓群这样犀利直言。 从汽车开始向电动化转型升级那天起&#xff0c;动力电池的安全隐患就一直是个老生常谈的话题了。曾毓群的这句话&#xff0c;直接点明了行业的发展…

ubuntu24.04 为什么扬声器没有声音,但是戴上耳机有声音

扬声器在 Ubuntu 24.04 下没有声音&#xff0c;但耳机有声音&#xff0c;可能是由于以下几个原因造成的&#xff1a; 1. 输出设备设置问题 系统可能将默认输出设备设置为耳机&#xff0c;而非扬声器。你可以检查或更改音频输出设备&#xff1a; 打开“设置” -> “声音”…

InternVL2- dockerfile环境变量持久化使用`ENV`而不是`RUN export`来设置环境变量,以确保环境变量在容器运行时仍然可用

在Dockerfile中使用RUN export命令设置环境变量并不是一种持久化的方式。当你在Dockerfile中使用export命令时&#xff0c;它只会在当前构建阶段生效&#xff0c;并不会被持久化到生成的镜像中。这是因为export命令实际上是在shell环境中设置环境变量&#xff0c;而Docker构建过…

推荐7款可以写论文的AI免费工具,原创一键生成神器!

在当今学术研究和写作领域&#xff0c;AI技术的应用越来越广泛&#xff0c;特别是在论文写作方面。为了帮助学生和研究人员提高写作效率和质量&#xff0c;以下推荐7款可以写论文的AI免费工具&#xff0c;这些工具均具备一键生成高质量论文的功能&#xff0c;是原创写作的神器。…

HarmonyOS应用开发( Beta5.0)一杯冰美式的时间“拿捏Grid组件”

常见情形 在很多手机商城的页面中会出现类似网格状一样的情况&#xff0c;例如&#xff1a; 京东 ​这里呢是采用Grid组件中的控制滚动,里面的rowsTemplate属性为一行且不对列的行数属性进行操作&#xff0c;这样的话就可以控制水平滑动了。 2.淘宝 ​ 这里就是极其简单的2*…

vscode spring boot项目编辑yaml不自动提示补全如何解决

文章目录 properties能够自动弹出提示但是YAML文件就不会自动弹出提示ctrl空格不出提示的解决办法 properties能够自动弹出提示 但是YAML文件就不会自动弹出提示 只是不会自动弹出来而已&#xff0c;按ctrl空格即可解决 ctrl空格不出提示的解决办法 如果按ctrl空格没有用 …

Python计算机视觉第九章-图像分割

目录 9.1 图割&#xff08;Graph Cut&#xff09; 9.1.1 从图像创建图 9.1.2 用户交互式分割 9.2 利用聚类进行分割 9.3 变分法 9.1 图割&#xff08;Graph Cut&#xff09; 图论中的图&#xff08;graph&#xff09;是由若干节点&#xff0…

Go语言 管道1

本篇文章主要介绍Go语言 无缓冲管道和有缓冲管道概念&#xff0c;特点及其使用示例。 目录 无缓冲通道 有缓冲的管道 语法 特点 代码示例 未分配空间示例 读取次数不一致示例 For-range遍历 总结 无缓冲通道 sync.RWMutex{} 当涉及到多go程时&#xff0c;c语言使用互…

Vue3实现打印功能

1、安装插件 npm i vue3-print-nb --save 2、main.js全局配置 import print from vue3-print-nb app.use(print) 3、设置打印区域 为打印区域设置 id 选择器 <div id"printData"><el-table border :data"tableData" style"width: 100%…

[DCVRP] 基于复杂网络的k-opt算法解空间表示(五)

基于复杂网络的k-opt算法解空间表示 如果想提高算法,了解解空间结构是一个很好的突破口。使用 节点表示可行解,边表示可行解之间的领域关系。然后通过计算法复杂网络的基本指标分析算法解空间结构,其目的是得出优秀算法的解空间结构所呈现的特征,基于分析结论设计一个算法…

Python——贪吃蛇

以下是一个简单的贪吃蛇游戏的Python代码示例&#xff1a; import pygame import time import random# 初始化 Pygame pygame.init()# 定义颜色 BLACK (0, 0, 0) WHITE (255, 255, 255) RED (255, 0, 0) GREEN (0, 255, 0) BLUE (0, 0, 255)# 设置屏幕尺寸 screen_width …

[000-01-008].第05节:OpenFeign高级特性-超时控制

我的后端学习大纲 SpringCloud学习大纲 1.1.OpenFeign超时的情况&#xff1a; 在Spring Cloud微服务架构中&#xff0c;大部分公司都是利用OpenFeign进行服务间的调用&#xff0c;而比较简单的业务使用默认配置是不会有多大问题的&#xff0c;但是如果是业务比较复杂&#xff…

【系统架构设计师-2017年真题】案例分析-答案及详解

更多内容请见: 备考系统架构设计师-核心总结索引 文章目录 【材料1】问题1问题2【材料2】问题1问题2问题3【材料3】问题1问题2问题3【材料4】问题1问题2问题3【材料5】问题1问题2问题3【材料1】 阅读以下关于软件架构评估的叙述,在答题纸上回答问题1和问题2。 【说明】某单位…

SpringMVC基于注解使用:上传下载

01-文件下载 基于servlet api的文件下载 注意一点content-disposition是以文件下载的方式打开意思是客户端地址栏不会改变&#xff0c; 如果注销了那句话就会跳转到下载图片的图片里面去&#xff0c;就在网页中显示了 基于spring ResponseEntity的文件下载 不支持缓冲区 一次…

【C++】Linux平台C++实现简单socket通信

Unix 域套接字 Unix 域套接字非常适合在同一台机器上的不同进程之间进行高效的通信。由于它们不需要网络协议栈&#xff0c;因此在性能上通常优于 TCP/IP 套接字。 Socket server端 StartSocketServer.cpp源代码&#xff1a; #include <iostream> #include <unist…

【HTML】Html标签

目录 结构盒子div 标签语义化标签 文本p 段落标签h 标题标签span 行内标签a 超链接标签br 换行标签、hr水平线标签sub 下标字、sup 上标字strong 或 b 加粗、em 或 i 斜体、del 或 s 删除线、ins 或 u 下划线marquee 滚动标签 列表ul 无序列表ol 有序列表dl 自定义列表列表嵌套…

Android 车联网——CarProperty使用实例(二十三)

在熟悉了 Car 下的相关 Manager 和 Service 后,这里我们通过需求来实现 CarProperty 的使用实例。 一、使用实例 1、需求分析 假如有这样一个需求,需要封装一个 SDK 为 APP 提供车辆信息、空调状态以及驾驶信息等相关属性的值及变化情况。这里我们首先需要确认各种属性对应…

取消Cursor的注释斜体字风格

1. 打开settings.json 2. 添加如下代码 "editor.tokenColorCustomizations": {"textMateRules": [{"name": "Comment","scope": ["comment","comment.block","comment.block.documentation"…