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

devtools/2025/1/20 3:04:44/

文章目录

      • 信号量的基本概念
      • 信号量的基本操作
      • 信号量的使用场景
      • 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/devtools/115293.html

相关文章

PL/SQL程序设计入门

PL/SQL程序设计 PL/SQL起步鼻祖&#xff1a;hello World语法分析声明部分举例 应用举例 PL/SQL 起步鼻祖&#xff1a;hello World 先举个例子&#xff0c;用PL/SQL打印输出hello world declarev_string varchar2(20); beginv_string:hello world;dbms_output.put_line(v_str…

Oracle发送邮件功能:配置自动化发信指南?

Oracle发送邮件服务设置方法&#xff1f;怎么用Oracle数据库发信&#xff1f; Oracle数据库作为企业级应用的核心&#xff0c;其内置的发送邮件功能为企业提供了强大的自动化工具。AokSend将详细介绍如何配置Oracle发送邮件功能&#xff0c;以实现自动化发信&#xff0c;从而提…

Vue使用axios二次封装、解决跨域问题

1、什么是 axios 在实际开发过程中&#xff0c;浏览器通常需要和服务器端进行数据交互。而 Vue.js 并未提供与服务器端通信的接口。从 Vue.js 2.0 版本之后&#xff0c;官方推荐使用 axios 来实现 Ajax 请求。axios 是一个基于 promise 的 HTTP 客户端。 关于 promise 的详细介…

TCP 和 UDP 协议的区别?

参考TCP 和 UDP的区别_tcp和udp的区别-CSDN博客

C语言习题~day35

1. int f(int x){ return ((x>2) ? x*f(x-1) : 3); } int i; if(f(2)) 执行如上函数后. i的值为&#xff08;&#xff09; A.30 B.无限递归 C.9 D.2160 先计算内层的f(2)&#xff0c;因为 2 不大于 2&#xff0c;所以返回 3&#xff0c;即f(2)3。 然后计算f(f(2))…

使用 Python 绘制 BTC 期权的波动率曲面

波动率曲面&#xff08;Volatility Surface&#xff09;是期权交易中展示隐含波动率随行权价&#xff08;strike price&#xff09;和到期时间&#xff08;expiry time&#xff09;变化的一种三维图形。 本文尝试通过 Python&#xff0c;通过 ccxt 基于从交易所获取期权的指标…

函数组件、Hooks和类组件区别

1. 函数组件&#xff08;Function Components&#xff09; 函数组件是接收props并返回React元素的纯JavaScript函数。它们不能拥有自己的状态&#xff08;state&#xff09;或生命周期方法&#xff0c;但在React 16.8中引入Hooks之后&#xff0c;这种情况发生了变化。 特点&a…

【运营攻略】怎样进行游戏产品的定位

关于游戏商业化设计的一些思考 - 游戏干饭之家 游戏运营进阶&#xff1a;数据分析驱动游戏版本调优 - 游戏干饭之家 在深入挖掘某款游戏数据时&#xff0c;发现了一个有趣的现象&#xff1a;70%的玩家好友数量在1-5个之间&#xff0c;11%的玩家好友数在6-10个&#xff0c;而平…