mutex专用于保护临界资源的互斥访问,具有所有权且仅加锁线程可解锁;信号量是计数器,用于控制有限资源的并发访问数量,无所有权限制。

Linux多线程编程中,Mutex(互斥锁)和信号量(Semaphore)是两种核心同步机制,用途不同、语义不同、适用场景也不同。用错会导致死锁、竞态或性能下降,理解本质差异比记住API更重要。
Mutex:专用于“同一时刻只允许一个线程访问临界资源”
Mutex 是二元状态(locked/unlocked),且具有所有权概念——只有加锁的线程才能解锁,不支持跨线程释放。它不是计数器,也不用于线程间协调数量关系。
- 适合保护共享变量、链表节点、文件描述符等需要原子访问的临界区
- 必须配对使用:
pthread_mutex_lock()/pthread_mutex_unlock(),避免忘记解锁引发死锁 - 推荐初始化为
PTHREAD_MUTEX_INITIALIZER(静态)或pthread_mutex_init()(动态),用完调pthread_mutex_destroy() - 可选属性如递归锁(
PTHREAD_MUTEX_RECURSIVE)用于同一线程多次加锁场景,但会掩盖设计问题,慎用
信号量:用于“控制多个线程对有限资源的并发访问数量”
信号量是整型计数器,通过 sem_wait()(P操作,减1阻塞)和 sem_post()(V操作,加1唤醒)工作。没有所有权限制,任意线程都可 post。
- 适用于生产者-消费者模型中的缓冲区槽位计数、线程池任务队列长度控制、资源池(如数据库连接数)管理
- 初始化时指定初始值,例如
sem_init(&sem, 0, 3)表示最多3个线程可同时进入某区域 - 注意区分 POSIX 有名信号量(
sem_open)与无名信号量(sem_init),多线程通常用无名信号量 - 务必在进程/线程退出前调用
sem_destroy(),否则可能泄漏内核资源
别把 Mutex 当信号量用,也别用信号量替代 Mutex
常见误用:
- 用
sem_init(&s, 0, 1)模拟 Mutex —— 看似等效,但丢失了所有权检查,容易因异常路径漏调sem_post导致永久阻塞 - 在临界区内调用
sem_wait()再次等待另一个信号量 —— 若未妥善处理超时或取消点,极易形成循环等待 - 多个 Mutex 加锁顺序不一致 —— 必须约定全局顺序(如按地址大小、按模块层级)来预防死锁
实际编码建议
优先选 Mutex:只要目标是“保护一段代码不被并发执行”,就用 Mutex。它轻量、语义清晰、错误易发现。
- 临界区尽量短,避免在锁内做 I/O、sleep、malloc 等可能阻塞或分配的操作
- 考虑使用读写锁(
pthread_rwlock_t)替代 Mutex,当读多写少且数据结构支持并发读时 - 调试阶段开启
-DDEBUG宏配合pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK),让非法重入或解锁立即报错 - 信号量更适合表达“资源可用性”,比如:3个 GPU 卡、5个 HTTP 连接槽、10个空闲内存块
不复杂但容易忽略:Mutex 解决的是“谁在用”,信号量解决的是“还能用几个”。分清这个边界,同步逻辑就清晰了。











