1737 字
9 分钟

POSIX无名信号量深度解析:用流水线思想玩转线程同步

POSIX无名信号量深度解析:用流水线思想玩转线程同步#

🎯 开篇:为什么你需要信号量?#

想象一下,你开了一家手工披萨店。厨房里三个师傅分别负责:

  • 师傅A:和面做饼底
  • 师傅B:涂抹酱料和芝士
  • 师傅C:烘烤和包装

如果三个师傅各干各的,会发生什么?

  • 师傅B可能把酱料涂在还没做好的饼底上
  • 师傅C可能把没加酱料的饼底直接放进烤箱
  • 顾客拿到的是一团糟的”披萨”

这就是线程同步要解决的问题!在多线程程序中,我们需要一种机制来协调不同线程的执行顺序,就像协调三个披萨师傅的工作流程一样。

🔧 信号量是什么?#

信号量(Semaphore)就像厨房里的订单铃铛

  • 当师傅A完成饼底制作,他会按一下铃铛(信号量+1)
  • 师傅B听到铃铛后,开始涂抹酱料,完成后按下一个铃铛(信号量+1)
  • 师傅C听到铃铛后,开始烘烤包装

在编程世界里,信号量是一个计数器,用来控制多个线程对共享资源的访问。

🎨 无名信号量 vs 有名信号量#

POSIX信号量分为两类:

类型特点使用场景
无名信号量内存中的信号量,随进程结束而消失线程间同步(同一进程内)
有名信号量有名字的信号量,可跨进程使用进程间同步

今天的主角是无名信号量,它就像厨房里的内部通讯系统,只在店铺内部使用。

💻 代码实战:电子工厂流水线#

让我们用一个电子工厂的例子来理解信号量的使用。工厂有三个工作站:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
// 定义三个信号量,对应三个工作环节
sem_t sem1_welding; // 焊接元器件环节
sem_t sem2_check; // 检查元器件环节
sem_t sem3_packing; // 打包发货环节

🔍 信号量初始化详解#

// 初始化信号量的正确姿势
sem_init(&sem1_welding, 0, 1); // 初始值为1,表示焊接环节可以开始
sem_init(&sem2_check, 0, 0); // 初始值为0,等待焊接完成
sem_init(&sem3_packing, 0, 0); // 初始值为0,等待检查完成

参数解释

  • 参数1:&sem1_welding - 信号量指针
  • 参数2:0 - 0表示线程间共享,非0表示进程间共享
  • 参数3:1 - 信号量初始值

🏭 三个工作线程的实现#

1️⃣ 焊接工作站#

void* Ttask1_Welding(void* arg)
{
while (1)
{
// P操作:等待可以开始焊接的信号
sem_wait(&sem1_welding);
printf("======================\n");
printf("(1)、焊接元器件!\n");
// V操作:通知检查环节可以开始了
sem_post(&sem2_check);
sleep(1); // 模拟工作时间
}
}

2️⃣ 质检工作站#

void* Ttask2_Check(void* arg)
{
while (1)
{
// P操作:等待焊接完成的信号
sem_wait(&sem2_check);
printf("(2)、检查元器件!\n");
// V操作:通知打包环节可以开始了
sem_post(&sem3_packing);
sleep(1); // 模拟工作时间
}
}

3️⃣ 包装工作站#

void* Ttask3_Packing(void* arg)
{
while (1)
{
// P操作:等待检查完成的信号
sem_wait(&sem3_packing);
printf("(3)、打包发货!\n");
// V操作:通知焊接环节可以开始下一轮了
sem_post(&sem1_welding);
sleep(1); // 模拟工作时间
}
}

🎮 主函数:启动流水线#

int main(int argc, char const *argv[])
{
pthread_t task1_welding_pid;
pthread_t task2_check_pid;
pthread_t task3_packing_pid;
// 创建三个工作线程
pthread_create(&task1_welding_pid, NULL, Ttask1_Welding, NULL);
pthread_create(&task2_check_pid, NULL, Ttask2_Check, NULL);
pthread_create(&task3_packing_pid, NULL, Ttask3_Packing, NULL);
while(1); // 主线程保持运行
// 清理工作(实际上不会执行到)
sem_destroy(&sem1_welding);
sem_destroy(&sem2_check);
sem_destroy(&sem3_packing);
return 0;
}

🔄 P操作和V操作的底层原理#

📉 P操作(sem_wait)#

sem_wait(&sem1_welding);

内部实现

  1. 检查信号量值是否大于0
  2. 如果大于0,信号量值减1,继续执行
  3. 如果等于0,线程阻塞等待

就像工人说:“有材料吗?有的话我拿走一个开始工作,没有的话我等会儿。”

📈 V操作(sem_post)#

sem_post(&sem2_check);

内部实现

  1. 信号量值加1
  2. 如果有线程在等待,唤醒其中一个

就像工人说:“我完成工作了,放一个成品到传送带上!”

🎪 运行效果分析#

程序运行后会看到:

======================
(1)、焊接元器件!
(2)、检查元器件!
(3)、打包发货!
======================
(1)、焊接元器件!
(2)、检查元器件!
(3)、打包发货!
...

完美实现了

  • 焊接 → 检查 → 打包的顺序执行
  • 每个环节等待前一个环节完成
  • 循环往复,永不停歇

⚠️ 常见踩坑指南#

💥 坑点1:忘记初始化信号量#

// ❌ 错误:使用未初始化的信号量
sem_t sem;
sem_wait(&sem); // 未定义行为!
// ✅ 正确:先初始化再使用
sem_init(&sem, 0, 1);
sem_wait(&sem);

💥 坑点2:信号量值设置错误#

// ❌ 错误:所有信号量都初始化为1
sem_init(&sem1, 0, 1);
sem_init(&sem2, 0, 1); // 应该为0!
sem_init(&sem3, 0, 1); // 应该为0!
// 这样会导致所有线程同时开始,没有同步效果

💥 坑点3:忘记销毁信号量#

// ❌ 错误:程序退出前不销毁信号量
// 可能导致资源泄漏
// ✅ 正确:使用完销毁信号量
sem_destroy(&sem);

💥 坑点4:P/V操作不匹配#

// ❌ 错误:P操作多于V操作
sem_wait(&sem); // P操作
sem_wait(&sem); // 又一个P操作
// 信号量可能变成负值,线程永久阻塞
// ✅ 正确:P/V操作要成对出现
sem_wait(&sem); // P操作
// ... 做一些工作 ...
sem_post(&sem); // V操作

🎨 生活化类比总结#

编程概念生活类比关键点
信号量餐厅座位计数器控制同时就餐人数
P操作顾客进店座位数减1,没座位就排队
V操作顾客离店座位数加1,通知排队顾客
初始值餐厅总座位数决定最大并发量

🚀 进阶思考#

🤔 如果改变初始值会怎样?#

sem_init(&sem1_welding, 0, 3); // 改为3

这样会有3个产品同时进入流水线,形成并行流水线

🤔 如何实现生产者-消费者模式?#

sem_t empty; // 空缓冲区数量
sem_t full; // 满缓冲区数量
sem_t mutex; // 互斥信号量

🤔 信号量 vs 互斥锁#

特性信号量互斥锁
用途同步互斥
取值0~N0~1
所有权
灵活性

🎯 总结:信号量使用三步走#

  1. 初始化:设置合适的初始值
  2. 使用:P/V操作成对出现
  3. 清理:用完记得销毁

记住:信号量就像交通信号灯,让多线程程序有条不紊地运行,避免”交通事故”!

📚 延伸阅读#

想要深入学习?可以探索:

  • POSIX有名信号量(跨进程同步)
  • System V信号量
  • 条件变量(另一种同步机制)
  • 读写锁(读者-写者问题)

编程就像做菜,信号量就是你的厨房计时器。用好了,多线程程序就能像米其林餐厅一样井然有序! 🍳✨

POSIX无名信号量深度解析:用流水线思想玩转线程同步
https://demo-firefly.netlify.app/posts/posix-unnamed-semaphores-thread-synchronization/
作者
长琴
发布于
2024-06-20
许可协议
CC BY-NC-SA 4.0
最后更新于 2024-06-20,距今已过 519 天

部分内容可能已过时

评论区

目录

Loading ... - Loading ...
封面
Loading ...
Loading ...
0:00 / 0:00