同步、异步与互斥:从通用OS到RTOS的全面解析
本文系统介绍了进程与线程的概念及区别,重点解析了同步、异步和互斥三大并发编程核心机制。在通用操作系统(如Linux)中,同步通过互斥量、信号量等实现,异步则依赖epoll、AIO等技术;而实时操作系统(RTOS)更强调确定性,通过二值信号量、事件标志组等实现同步。文章通过生产者-消费者模型示例,展示了多线程编程中如何协调同步与互斥,并对比了通用OS与RTOS在验证方法上的差异。最后提供了完整的多线
一、基础概念:进程与线程
1.1 什么是进程?
进程是操作系统进行资源分配和调度的基本单位,是一个正在运行的程序实例。
1.2 什么是线程?
线程是操作系统进行CPU调度的基本单位,是进程内部的一条执行路径(轻量级进程)。
1.3 经典类比
-
进程 = 一间房子(独立空间、水电)
-
线程 = 房子里的多个工人(共享房间环境,各自干活)
1.4 进程与线程的核心区别
| 对比项 | 进程 | 线程 |
|---|---|---|
| 资源分配单位 | ✅ 是 | ❌ 否 |
| CPU调度单位 | ❌ 否 | ✅ 是 |
| 内存空间 | 独立 | 共享进程资源 |
| 创建/切换开销 | 大 | 小 |
| 通信方式 | IPC(复杂) | 共享变量(简单,需同步) |
| 崩溃影响 | 不影响其他进程 | 导致整个进程崩溃 |
二、核心概念:同步、异步与互斥
2.1 同步(Synchronization)
-
定义:一个任务发起操作后,必须等待该操作完成才能继续执行。
-
特点:阻塞、顺序、可控。
-
例子:线程A调用
read()读取文件,文件未读完前线程A被挂起。 -
风险:若操作耗时,会导致任务卡死。
2.2 异步(Asynchronous)
-
定义:一个任务发起操作后,无需等待操作完成,立即继续执行后续代码。
-
特点:非阻塞、并发、效率高。
-
例子:Node.js异步I/O,发起网络请求后立刻执行下一行代码。
-
风险:逻辑复杂(回调地狱)、状态管理难。
2.3 互斥(Mutual Exclusion)
-
定义:防止多个任务同时访问同一共享资源,保证资源的完整性。
-
特点:一次只允许一个任务进入临界区。
-
例子:两个线程同时修改全局变量,通过加锁防止数据错乱。
2.4 同步 vs 互斥 —— 一句话区分
| 维度 | 同步 | 互斥 |
|---|---|---|
| 核心问题 | “能不能走?”(协调顺序) | “能不能进?”(保护资源) |
| 典型场景 | 生产者没生产完,消费者不能取 | 两个线程同时修改全局变量 |
| 类比 | 接力棒(跑完第一棒才能跑第二棒) | 厕所门锁(一次进一个人) |
注意:在并发编程中,“同步”还有另一层含义——指控制多个任务对共享资源的访问顺序(即互斥+同步的总称)。这与“同步操作 vs 异步操作”中的“同步”含义不同,需根据上下文区分。
三、通用OS中的同步与异步
通用OS:Windows、Linux、macOS、Android/iOS等,强调功能全面和用户体验。
3.1 同步机制(通用OS)
| 机制 | 说明 | 典型API |
|---|---|---|
| 互斥量 | 保护共享资源,防止并发访问 | pthread_mutex_lock/unlock |
| 信号量 | 控制资源访问数量或任务同步 | sem_wait/sem_post |
| 条件变量 | 等待某个条件满足后再继续 | pthread_cond_wait/signal |
| 读写锁 | 允许多读单写 | pthread_rwlock_rdlock/wrlock |
3.2 异步机制(通用OS)
| 机制 | 说明 | 典型API |
|---|---|---|
| 信号 | 进程间异步通知 | signal(), kill() |
| AIO | 异步I/O操作 | aio_read, aio_error |
| epoll/IOCP | 事件驱动,不轮询 | epoll_create, epoll_wait |
| std::async | C++异步任务 | std::async, future.get() |
四、RTOS中的同步与异步
RTOS:实时操作系统(如FreeRTOS),强调确定性和时间约束。
4.1 RTOS中的同步机制
RTOS中的同步指任务与任务之间或任务与中断之间的协调机制。
| 机制 | 用途 | 关键特点 |
|---|---|---|
| 二值信号量 | 任务等待中断 | 无优先级继承,适合同步 |
| 计数信号量 | 多资源管理/生产消费 | 可计数 |
| 事件标志组 | 等待多个事件(与/或) | 支持复杂触发条件 |
| 消息队列 | 任务间传递数据 | 带阻塞超时 |
典型同步示例:
-
任务A调用
xSemaphoreTake()阻塞,等待传感器数据 -
中断ISR采集完数据,调用
xSemaphoreGiveFromISR()释放信号量 -
任务A立即解除阻塞,处理数据
4.2 RTOS中的异步机制
RTOS核心API大多是同步阻塞型(如xQueueSend可设置超时)。异步行为通常通过中断+信号量/队列模拟:
| 异步场景 | 实现方式 |
|---|---|
| 中断异步通知 | ISR释放信号量后立即返回,不等待任务处理 |
| 任务异步消息 | 任务A发送消息后继续执行,不等待接收方处理 |
| 定时器回调 | 软件定时器回调在任务上下文中异步执行 |
4.3 RTOS中的“同步”与“互斥”区分
| 对比项 | 同步 | 互斥 |
|---|---|---|
| 推荐工具 | 二值信号量 | 互斥量 |
| 优先级继承 | ❌ 不需要 | ✅ 必须(防优先级翻转) |
| 是否可在中断中使用 | ✅ 可以(GiveFromISR) | ❌ 不可以 |
| 典型场景 | 任务等待中断 | 保护共享资源 |
五、同步、异步、互斥三者的关系
| 概念 | 核心问题 | 典型工具(通用OS) | 典型工具(RTOS) |
|---|---|---|---|
| 同步 | 顺序协调(能不能走) | 条件变量、信号量 | 二值信号量、事件标志组 |
| 异步 | 不等待结果 | epoll、AIO、信号 | 中断+信号量/队列 |
| 互斥 | 资源保护(能不能进) | 互斥量、读写锁 | 互斥量(优先级继承) |
三者可以共存:一个典型的生产者-消费者模型中:
互斥保护缓冲区(防止同时修改)
同步协调生产者和消费者(缓冲区空时消费者等待)
异步:生产者生产后不等待消费者处理,立即继续生产
六、验证方法
6.1 通用OS验证方法
同步机制验证示例(互斥量)
int counter = 0;
pthread_mutex_t mutex;
void* thread(void* arg) {
for(int i=0; i<10000; i++) {
pthread_mutex_lock(&mutex);
counter++;
pthread_mutex_unlock(&mutex);
}
}
// 验证:加锁后结果正确(20000),不加锁结果错乱
异步机制验证示例(epoll)
int epfd = epoll_create(1);
while(1) {
int nfds = epoll_wait(epfd, events, 10, -1); // 阻塞等待事件
// 验证:只有socket有数据时才返回,CPU空转为0%
for(...) handle_event();
}
6.2 RTOS验证方法
同步机制验证示例(响应延迟测量)
volatile uint32_t enter_time, exit_time;
void ISR() {
enter_time = DWT_CYCCNT; // 读CPU周期计数器
xSemaphoreGiveFromISR(sem, &woken);
portYIELD_FROM_ISR(woken);
}
void Task() {
xSemaphoreTake(sem, portMAX_DELAY);
exit_time = DWT_CYCCNT;
uint32_t latency_us = (exit_time - enter_time) / CPU_MHZ;
// 验证:延迟应在微秒级且基本恒定
}
异步机制验证示例(ISR不等待任务)
void ISR() {
xSemaphoreGiveFromISR(sem, NULL); // 立即返回,不等待
// 逻辑分析仪测量ISR出口到Task入口的时间差
}
void Task() {
xSemaphoreTake(sem, portMAX_DELAY);
GPIO_Toggle(LED); // 示波器测量此引脚
}
6.3 通用OS vs RTOS验证对比
| 验证维度 | 通用OS | RTOS |
|---|---|---|
| 主要目标 | 正确性(无竞争、死锁) | 正确性 + 实时性(延迟确定) |
| 核心工具 | 软件分析工具 | 硬件仪器(逻辑分析仪) |
| 关键指标 | 吞吐量、锁等待时间 | 中断延迟、响应时间、抖动 |
七、总结
| 核心概念 | 一句话总结 |
|---|---|
| 进程 | 资源分配的基本单位,拥有独立内存空间 |
| 线程 | CPU调度的基本单位,共享进程资源 |
| 同步 | 等待结果再继续(阻塞) |
| 异步 | 不等待结果,回头通知(非阻塞) |
| 互斥 | 保护共享资源,防止同时访问 |
| 同步 vs 互斥 | 同步问“能不能走”,互质问“能不能进” |
| 通用OS重点 | 公平性、吞吐量、功能丰富 |
| RTOS重点 | 确定性、实时性、可预测延迟 |
八、生产者-消费者模型(多线程编程)
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
// --- 配置参数 ---
#define BUFFER_SIZE 5 // 缓冲区大小
#define NUM_ITEMS 10 // 每个生产者生产的总数量
#define PRODUCER_NUM 1 // 生产者线程数
#define CONSUMER_NUM 2 // 消费者线程数
// --- 共享资源 ---
int buffer[BUFFER_SIZE]; // 环形缓冲区
int count = 0; // 缓冲区当前元素个数
int in = 0; // 生产者放入数据的索引
int out = 0; // 消费者取出数据的索引
// --- 同步原语 ---
pthread_mutex_t mutex; // 互斥锁
pthread_cond_t not_full; // 缓冲区未满条件变量
pthread_cond_t not_empty; // 缓冲区非空条件变量
// --- 生产者线程函数 ---
void *producer(void *arg) {
int id = *(int *)arg;
int item;
for (int i = 0; i < NUM_ITEMS; i++) {
item = rand() % 100; // 生产一个随机数据
// 1. 加锁
pthread_mutex_lock(&mutex);
// 2. 如果缓冲区满,等待
while (count == BUFFER_SIZE) {
pthread_cond_wait(¬_full, &mutex);
}
// 3. 生产数据(放入缓冲区)
buffer[in] = item;
in = (in + 1) % BUFFER_SIZE; // 环形索引
count++;
printf("生产者 %d 生产了: %d (当前库存: %d)\n", id, item, count);
// 4. 解锁
pthread_mutex_unlock(&mutex);
// 5. 通知消费者(缓冲区不空了)
pthread_cond_signal(¬_empty);
usleep(rand() % 100000); // 模拟生产耗时
}
return NULL;
}
// --- 消费者线程函数 ---
void *consumer(void *arg) {
int id = *(int *)arg;
int item;
// 消费者一直运行,直到手动停止或根据逻辑退出
// 这里为了演示,简单设定一个循环次数,实际中通常由特定信号退出
int consumed_count = 0;
while (consumed_count < NUM_ITEMS * PRODUCER_NUM / CONSUMER_NUM + 5) {
// 1. 加锁
pthread_mutex_lock(&mutex);
// 2. 如果缓冲区空,等待
while (count == 0) {
pthread_cond_wait(¬_empty, &mutex);
}
// 3. 消费数据(从缓冲区取出)
item = buffer[out];
out = (out + 1) % BUFFER_SIZE;
count--;
printf(" 消费者 %d 消费了: %d (当前库存: %d)\n", id, item, count);
// 4. 解锁
pthread_mutex_unlock(&mutex);
// 5. 通知生产者(缓冲区不满)
pthread_cond_signal(¬_full);
consumed_count++;
usleep(rand() % 150000); // 模拟消费耗时
}
return NULL;
}
int main() {
pthread_t pro_threads[PRODUCER_NUM];
pthread_t con_threads[CONSUMER_NUM];
int pro_ids[PRODUCER_NUM];
int con_ids[CONSUMER_NUM];
// 初始化互斥锁和条件变量
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(¬_full, NULL);
pthread_cond_init(¬_empty, NULL);
// 创建生产者线程
for (int i = 0; i < PRODUCER_NUM; i++) {
pro_ids[i] = i + 1;
pthread_create(&pro_threads[i], NULL, producer, &pro_ids[i]);
}
// 创建消费者线程
for (int i = 0; i < CONSUMER_NUM; i++) {
con_ids[i] = i + 1;
pthread_create(&con_threads[i], NULL, consumer, &con_ids[i]);
}
// 等待所有生产者结束
for (int i = 0; i < PRODUCER_NUM; i++) {
pthread_join(pro_threads[i], NULL);
}
// 等待消费者结束(这里简单等待一下,实际场景可能需要更复杂的退出机制)
sleep(2);
// 销毁同步对象
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(¬_full);
pthread_cond_destroy(¬_empty);
printf("主线程结束。\n");
return 0;
}
在 Linux 或 macOS 终端中,使用 gcc 编译并链接 pthread 库:
gcc producer_consumer.c -o pc_demo -lpthread
./pc_demo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)