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

操作系统学习(八)——同步

一、同步

在操作系统和并发编程中,同步(Synchronization) 是指协调多个线程或进程之间的执行顺序,以保证程序的正确性与一致性
同步的核心目的在于解决共享资源访问、线程调度顺序等问题,避免竞态条件(Race Condition)、数据不一致或死锁

同步 = 保证多个线程/进程按照一定顺序协作执行,共享数据的一致性与完整性得以维护。

举例说明:

  • 两个线程同时修改一个全局变量,如果不进行同步,最终结果可能错误。
  • 消费者线程不能消费一个还没被生产者生产出来的数据项,这就需要同步。

二、同步 vs 互斥

对比项互斥(Mutual Exclusion)同步(Synchronization)
目的防止多个线程同时访问共享资源保证多个线程按指定顺序协作执行
关键字关键段、互斥锁信号量、条件变量、屏障等
是否通信否,侧重排他是,侧重协调和等待/通知
示例多线程写同一文件需加锁消费者等待生产者生产数据

三、常见同步机制

同步机制描述
信号量(Semaphore)用于控制访问共享资源的数量或线程的同步
条件变量(Condition Variable)线程等待某个条件成立后被唤醒
屏障(Barrier)多个线程相互等待,全部达到屏障点再继续
事件(Event)用于线程之间通知/唤醒
原子操作(Atomic)无需锁的同步方式(如 std::atomic

四、同步机制详细解析

1. 信号量(Semaphore)

  • 是一个计数器变量,用于控制多个线程对资源的访问。

  • 常见两种类型:

    • 二值信号量(Binary Semaphore):只允许一个线程访问(类似互斥锁);
    • 计数信号量(Counting Semaphore):允许多个线程同时访问资源。

POSIX 信号量 API:

函数描述
sem_init初始化信号量
sem_wait(P操作)等待信号量,值减1
sem_post(V操作)释放信号量,值加1
sem_destroy销毁信号量

示例1(POSIX)—— 互斥(代替互斥锁):

sem_t mutex;
sem_init(&mutex, 0, 1);  // 初始化为 1(只有1个线程能进入)// 线程代码
sem_wait(&mutex);   // 等待进入临界区
// 访问共享资源
sem_post(&mutex);   // 离开临界区,释放资源

示例2(POSIX)—— 线程同步:
示例:一个线程等待另一个线程完成某事。

sem_t ready;
sem_init(&ready, 0, 0);void* producer(void*) 
{// 做一些准备工作sem_post(&ready); // 通知消费者
}void* consumer(void*) 
{sem_wait(&ready); // 等待生产者通知// 开始消费
}

示例3(POSIX)—— 资源计数:
控制同一时间最多 N 个线程进入某区域。

sem_t sem;
sem_init(&sem, 0, N); // 最多允许 N 个并发void* worker(void*) 
{sem_wait(&sem);   // 有“票”才能进入// 执行任务sem_post(&sem);   // 释放“票”
}

2. 条件变量(Condition Variable)

  • 用于线程等待一个特定条件的发生;
  • 与互斥锁搭配使用。

关键函数接口:

函数作用
pthread_cond_init()初始化条件变量
pthread_cond_wait()等待条件,阻塞线程
pthread_cond_signal()唤醒一个等待线程
pthread_cond_broadcast()唤醒所有等待线程
pthread_cond_destroy()销毁条件变量

示例(POSIX,生产者-消费者):

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int ready = 0;void* producer(void* arg) 
{sleep(1); // 模拟生产时间pthread_mutex_lock(&mutex);ready = 1;printf("生产者:资源已就绪,通知消费者\n");pthread_cond_signal(&cond);  // 通知消费者pthread_mutex_unlock(&mutex);return NULL;
}void* consumer(void* arg) 
{pthread_mutex_lock(&mutex);while (!ready) {printf("消费者:等待资源...\n");pthread_cond_wait(&cond, &mutex);  // 自动解锁并阻塞,直到被唤醒}printf("消费者:收到通知,开始处理资源\n");pthread_mutex_unlock(&mutex);return NULL;
}int main() 
{pthread_t prod, cons;pthread_create(&prod, NULL, producer, NULL);pthread_create(&cons, NULL, consumer, NULL);pthread_join(prod, NULL);pthread_join(cons, NULL);pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;
}

3. 屏障(Barrier)

关键函数接口:

函数名作用
pthread_barrier_init()初始化屏障对象
pthread_barrier_wait()等待屏障,同步点
pthread_barrier_destroy()销毁屏障对象
  • 用于多线程并发场景,强制线程等待,直到全部线程都到达屏障点,才统一继续执行。

示例(POSIX):

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>#define THREAD_COUNT 3
pthread_barrier_t barrier;void* task(void* arg) 
{int id = *(int*)arg;printf("线程 %d 开始第一阶段工作\n", id);sleep(id);  // 模拟不同线程执行速度printf("线程 %d 到达屏障,等待其他线程...\n", id);pthread_barrier_wait(&barrier);  // 所有线程到达后继续printf("线程 %d 通过屏障,开始第二阶段工作\n", id);return NULL;
}int main() {pthread_t threads[THREAD_COUNT];int ids[THREAD_COUNT];pthread_barrier_init(&barrier, NULL, THREAD_COUNT);for (int i = 0; i < THREAD_COUNT; ++i) {ids[i] = i + 1;pthread_create(&threads[i], NULL, task, &ids[i]);}for (int i = 0; i < THREAD_COUNT; ++i)pthread_join(threads[i], NULL);pthread_barrier_destroy(&barrier);return 0;
}

4. 原子操作(Atomic)

  • 使用底层硬件指令支持的原子性操作,无需加锁
  • 适用于简单变量(计数器、自增等)。

示例(C++):

#include <atomic>
std::atomic<int> counter = 0;void thread_func() 
{counter++; // 原子自增,无需加锁
}

五、同步的经典问题

1. 生产者-消费者问题

  • 问题描述:一个线程生产数据,一个线程消费,必须保证:生产→消费→再生产→…
  • 解决方式:信号量 + 条件变量。

2. 哲学家就餐问题

  • 问题描述:五位哲学家围坐,每人要用左右筷子才能吃饭,互相抢资源导致死锁。
  • 解决方式:通过设定拿筷子的顺序、加入等待/超时机制解决同步问题。

六、同步存在的问题

问题描述
死锁多个线程相互等待资源,永远阻塞
优先级反转高优线程被低优线程阻塞
忙等待无效循环等待条件满足,浪费CPU
性能下降同步过多导致串行执行

七、应用场景

场景所需同步机制
限制并发访问数据库连接池信号量
多线程按顺序执行任务条件变量
并行计算中的任务阶段同步屏障
共享数据的并发计数原子变量

八、同步的优化

  • 避免过度同步,降低锁粒度,提高并发度;
  • 用原子操作就不用锁;
  • 使用线程安全的数据结构(如线程安全队列);
  • 尽量避免线程之间复杂依赖,降低死锁概率;
  • 使用现代语言提供的高层抽象(如 C++ std::future, Java CountDownLatch)。
http://www.xdnf.cn/news/747145.html

相关文章:

  • 【python深度学习】Day 41 简单CNN
  • STM32F103通过Zigbee实现多分用户向主用户发送信息
  • LeetCode Hot100 (贪心)
  • VS Code / Cursor 将默认终端设置为 CMD 完整指南
  • 算法打卡12天
  • Leetcode LCR 187. 破冰游戏
  • cuda_fp8.h错误
  • Python 中Vector类的格式化实现,重点拆解其超球面坐标系的设计精髓
  • C# 面向对象特性
  • 吉林第三届全国龙舟邀请赛(大安站)激情开赛
  • 打卡day41
  • Kanass入门教程- 事项管理
  • 科普:Linux `su` 切换用户后出现 `$` 提示符,如何排查和解决?
  • 山东大学软件学院项目实训-基于大模型的模拟面试系统-面试官和面试记录的分享功能(2)
  • InfluxDB 高级函数详解:DERIVATIVE、INTEGRAL、SPREAD、HISTOGRAM 与 DIFFERENCE
  • [SC]SystemC在CPU/GPU验证中的应用(五)
  • 22睿抗省赛真题
  • DAY41
  • 【SLAM自救笔记1】:苟活
  • 【Netty系列】消息编码解码框架
  • LeetCode[110]平衡二叉树
  • 第6章 放大电路的反馈
  • AI Agent、Function Calling 与 MCP 协议的原理与实践
  • Linux系统-基本指令(4)
  • 评标专家随机抽选系统-建设方案——仙盟创梦IDE
  • WEB3——简易NFT铸造平台之nft.storage
  • 【知识点进阶】
  • Java 中 Redis 过期策略深度解析(含拓展-redis内存淘汰策略列举)
  • TI MSPM0G3507 简易PID项目显示和按键控制
  • [SLAM自救笔记0]:开端