Linux信号量
- 软件开发
- 2025-09-05 20:15:02

Linux 信号量 一、信号量基础概念1.1 同步机制的核心需求1.2 信号量的核心原理1.3 信号量类型对比 二、实战代码解析2.1 共享内存与信号量结合示例2.2 信号量类实现要点 三、关键实现细节分析3.1 初始化三步骤3.2 SEM_UNDO机制3.3 原子操作保证 四、进阶应用场景4.1 生产者-消费者模型4.2 读写锁实现 五、最佳实践建议六、常见问题排查七、现代替代方案 一、信号量基础概念 1.1 同步机制的核心需求
在多进程/多线程编程中,当多个执行单元需要访问共享资源时,必须引入同步机制来保证数据一致性。信号量(Semaphore)正是解决这一问题的经典方案。
1.2 信号量的核心原理信号量本质上是一个计数器,通过两个原子操作实现进程同步:
P操作(wait):申请资源,计数器减1V操作(post):释放资源,计数器加1 1.3 信号量类型对比 类型System V信号量POSIX信号量初始化方式需要显式初始化可静态初始化作用域系统级进程级性能较高开销较低开销功能复杂度支持信号量集合仅支持单个信号量 二、实战代码解析 2.1 共享内存与信号量结合示例 // 演示使用信号量给共享内存加锁 #include "_public.h" struct stgirl { int no; char name[51]; }; int main(int argc, char* argv[]) { if (argc != 3) { cout << "Usage: ./test no name\n"; return -1; } // 创建/获取共享内存 int shmid = shmget(0x5005, sizeof(stgirl), 0640|IPC_CREAT); stgirl* ptr = (stgirl*)shmat(shmid, 0, 0); // 初始化信号量 csemp mutex; mutex.init(0x5005); // 临界区保护 cout << "申请加锁...\n"; mutex.wait(); // 操作共享数据 cout << "原值: no=" << ptr->no << ", name=" << ptr->name << endl; ptr->no = atoi(argv[1]); strcpy(ptr->name, argv[2]); sleep(10); // 模拟耗时操作 mutex.post(); shmdt(ptr); return 0; } 2.2 信号量类实现要点 class csemp { public: bool init(key_t key, unsigned short value=1, short sem_flg=SEM_UNDO); bool wait(short sem_op=-1); bool post(short sem_op=1); // ...其他成员函数 private: int m_semid; short m_sem_flg; }; // 初始化流程图 graph TD A[开始] --> B{信号量是否存在?} B -- 存在 --> C[直接获取] B -- 不存在 --> D[创建新信号量] D --> E[设置初始值] E --> F[初始化完成] 三、关键实现细节分析 3.1 初始化三步骤 尝试获取现有信号量创建新信号量(IPC_EXCL保证原子性)设置初始值(仅创建者需要) 3.2 SEM_UNDO机制 作用:防止进程异常终止导致的死锁实现方式:内核维护调整记录适用场景:建议用于互斥锁场景 3.3 原子操作保证 struct sembuf sem_b; sem_b.sem_num = 0; sem_b.sem_op = -1; // P操作 sem_b.sem_flg = SEM_UNDO; semop(m_semid, &sem_b, 1); 四、进阶应用场景 4.1 生产者-消费者模型 // 初始化两个信号量 csemp empty(10); // 缓冲区空位 csemp full(0); // 已填充数量 // 生产者 empty.wait(); // 生产数据... full.post(); // 消费者 full.wait(); // 消费数据... empty.post(); 4.2 读写锁实现 csemp mutex(1); // 互斥锁 csemp writeLock(1);// 写锁 int readers = 0; // 读锁定 mutex.wait(); readers++; if (readers == 1) writeLock.wait(); mutex.post(); // 读解锁 mutex.wait(); readers--; if (readers == 0) writeLock.post(); mutex.post(); // 写锁定 writeLock.wait(); // 写解锁 writeLock.post(); 五、最佳实践建议命名规范
使用ftok生成唯一key示例:key_t key = ftok("/tmp", 'A');错误处理
if (semop(...) == -1) { if (errno == EINTR) { // 处理信号中断 } // 其他错误处理 }性能优化
优先考虑POSIX信号量避免过度使用信号量集合设置合理的超时机制调试技巧
使用ipcs -s查看信号量状态通过semctl获取当前值 六、常见问题排查ENOSPC错误
原因:系统信号量数量达到上限解决:sysctl -w kernel.sem="250 32000 100 128"EIDRM错误
现象:信号量被意外删除预防:增加引用计数机制死锁检测
使用pstack分析进程堆栈借助valgrind工具链检测 七、现代替代方案原子变量
std::atomic<int> counter(0); counter.fetch_add(1, std::memory_order_relaxed);文件锁
int fd = open("lockfile", O_CREAT|O_RDWR, 0644); flock(fd, LOCK_EX);RCU机制
适用读多写少场景无锁读取设计信号量作为经典的进程同步工具,在系统级编程中仍具有重要地位。理解其底层机制并结合现代编程范式,能够帮助开发者构建更健壮的并发系统。在实际应用中,需要根据具体场景选择最合适的同步策略,平衡性能与安全性。