前言
在 Linux 并发编程中,多线程、多进程共享资源竞争是最核心的问题。如果没有同步保护,会出现数据覆盖、逻辑错乱、日志乱序、库存超卖等一系列偶现 Bug。
日常开发中最常用的四大同步工具:互斥锁、读写锁、条件变量、文件锁,各自适配不同业务场景,很多开发者容易混淆用法、踩坑死锁、性能退化问题。
本文将统一标准化讲解四大机制:是什么、解决什么问题、全套函数参数解析、最小可运行代码、核心误区,看完即可彻底掌握 Linux 主流同步方案,适配面试、开发、排查问题。
一、互斥锁(Mutex)------ 最通用的排他锁
1. 核心概念 & 作用
定义 :互斥锁是 POSIX 标准的独占式同步锁,同一时刻仅允许**一个执行流(线程/进程)**持有锁、进入临界区。
解决痛点:解决多线程/跨进程共享资源的并发竞态问题,保证临界区代码串行执行,是通用性最强、最稳定的同步方案。
核心特性:排他访问、阻塞等待、简单易用、适配绝大多数临界区场景。
2. 全套函数声明 & 参数详解
cpp
#include <pthread.h>
// 1. 初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
// 2. 阻塞加锁:拿不到锁则线程休眠阻塞
int pthread_mutex_lock(pthread_mutex_t *mutex);
// 3. 非阻塞加锁:拿不到锁直接返回失败,不阻塞
int pthread_mutex_trylock(pthread_mutex_t *mutex);
// 4. 释放锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
// 5. 销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数解析:
-
mutex:互斥锁变量指针,操作的目标锁; -
attr:锁属性结构体,传 NULL 代表使用默认属性(线程共享、普通锁);
返回值 :所有函数成功返回 0,失败返回非 0 错误码。
初始化方式 :全局锁可直接静态初始化pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
3. 最小可运行代码示例
实现多线程计数器同步,解决并发计数错乱问题:
cpp
#include <stdio.h>
#include <pthread.h>
// 定义互斥锁
pthread_mutex_t mutex;
int count = 0;
// 线程执行函数:循环计数
void *count_task(void *arg) {
for (int i = 0; i < 100000; i++) {
pthread_mutex_lock(&mutex); // 加锁:进入临界区
count++; // 共享资源操作
pthread_mutex_unlock(&mutex); // 解锁:退出临界区
}
return NULL;
}
int main() {
// 初始化锁
pthread_mutex_init(&mutex, NULL);
pthread_t t1, t2;
// 创建两个并发线程
pthread_create(&t1, NULL, count_task, NULL);
pthread_create(&t2, NULL, count_task, NULL);
// 等待线程结束
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("最终计数结果:%d\n", count); // 固定输出 200000
// 销毁锁
pthread_mutex_destroy(&mutex);
return 0;
}
编译 :
gcc mutex_demo.c -o mutex_demo -pthread运行 :
./mutex_demo
4. 核心注意事项
-
禁止加锁后不解锁、重复解锁:忘解锁会导致线程永久阻塞,重复解锁会直接触发程序崩溃;
-
避免锁粒度过大:临界区包含无关代码、IO 操作,会大幅降低并发性能;
-
存在优先级反转问题,高优先级线程可能被低优先级线程阻塞,实时系统需配置优先级继承属性。
二、读写锁(RWLock)------ 读多写少场景性能神器
1. 核心概念 & 作用
定义 :读写锁是优化版的互斥锁,核心规则:读共享、写排他。
解决痛点 :普通互斥锁读写完全互斥,多读场景下并发极低。读写锁支持多线程同时读、写操作独占,大幅提升读多写少场景的并发吞吐量。
核心特性:读无冲突共享、写独占阻塞、适配配置读取、缓存查询场景。
2. 全套函数声明 & 参数详解
cpp
#include <pthread.h>
// 1. 初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
// 2. 加读锁:多线程可同时加锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
// 3. 加写锁:独占加锁,读写全部阻塞
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
// 4. 释放锁(读写锁通用)
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
// 5. 销毁读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
参数解析:
-
rwlock:读写锁变量指针; -
attr:锁属性,NULL 为默认读优先属性;
返回值:成功返回 0,失败返回错误码。
3. 最小可运行代码示例
模拟多线程读、单线程写的缓存场景:
cpp
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_rwlock_t rwlock;
int cache_data = 100;
// 读线程:并发读取数据
void *read_task(void *arg) {
int idx = *(int *)arg;
while (1) {
pthread_rwlock_rdlock(&rwlock);
printf("读线程%d:读取缓存数据 = %d\n", idx, cache_data);
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
return NULL;
}
// 写线程:定时修改数据
void *write_task(void *arg) {
while (1) {
pthread_rwlock_wrlock(&rwlock);
cache_data++;
printf("写线程:更新缓存数据 = %d\n", cache_data);
pthread_rwlock_unlock(&rwlock);
sleep(3);
}
return NULL;
}
int main() {
pthread_rwlock_init(&rwlock, NULL);
pthread_t t1, t2, t3;
int a=1, b=2;
pthread_create(&t1, NULL, read_task, &a);
pthread_create(&t2, NULL, read_task, &b);
pthread_create(&t3, NULL, write_task, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_rwlock_destroy(&rwlock);
return 0;
}
编译 :
gcc rwlock_demo.c -o rwlock_demo -pthread运行 :
./rwlock_demo
4. 核心注意事项
-
默认读优先,存在写者饥饿问题:持续大量读请求会导致写线程永久阻塞,高频更新场景需改为写优先锁属性;
-
写多场景不适用:读写锁维护开销高于普通互斥锁,写频繁场景用互斥锁性能更好。
三、条件变量(Condition Variable)------ 线程时序同步神器
1. 核心概念 & 作用
定义 :条件变量必须配合互斥锁使用,是专门用于线程等待特定条件成立的同步机制。
解决痛点:互斥锁只能解决互斥访问,无法解决「等待条件」问题。条件变量可以让线程条件不满足时休眠等待,条件达成后唤醒,避免空轮询耗 CPU,是生产者-消费者模型的标准实现方案。
核心特性:阻塞等待、主动唤醒、有序执行、零空耗 CPU。
2. 全套函数声明 & 参数详解
cpp
#include <pthread.h>
// 1. 初始化条件变量
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
// 2. 阻塞等待条件:自动解锁、休眠;唤醒后自动加锁
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
// 3. 唤醒一个等待线程
int pthread_cond_signal(pthread_cond_t *cond);
// 4. 唤醒所有等待线程
int pthread_cond_broadcast(pthread_cond_t *cond);
// 5. 销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
参数解析:
-
cond:条件变量指针; -
mutex:配套绑定的互斥锁,必须非空; -
attr:属性结构体,NULL 为默认属性。
3. 最小可运行代码示例
极简生产者-消费者模型:
cpp
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int ready = 0;
// 消费者线程:等待生产完成
void *consumer(void *arg) {
pthread_mutex_lock(&mutex);
// 必须while规避虚假唤醒
while (ready == 0) {
pthread_cond_wait(&cond, &mutex);
}
printf("消费者:收到数据,开始消费\n");
ready = 0;
pthread_mutex_unlock(&mutex);
return NULL;
}
// 生产者线程:生产后唤醒消费者
void *producer(void *arg) {
sleep(1); // 模拟生产耗时
pthread_mutex_lock(&mutex);
ready = 1;
printf("生产者:数据生产完成,唤醒消费者\n");
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, consumer, NULL);
pthread_create(&t2, NULL, producer, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_cond_destroy(&cond);
return 0;
}
编译 :
gcc cond_demo.c -o cond_demo -pthread运行 :
./cond_demo
4. 核心注意事项
-
禁止使用 if 判断条件,必须用 while:Linux 存在虚假唤醒(无信号自动唤醒),while 可二次校验条件,杜绝逻辑 Bug;
-
条件变量不能单独使用:必须绑定互斥锁,否则会出现线程竞争、程序崩溃。
四、文件锁(fcntl)------ 跨进程文件同步专属方案
1. 核心概念 & 作用
定义 :fcntl 文件锁是 Linux 系统原生的文件级同步机制,支持对文件任意区间加读锁/写锁。
解决痛点 :上述三种锁仅适用于线程同步 ,无法跨进程生效。文件锁专门解决多进程并发读写同一文件的错乱、覆盖问题(日志写入、配置更新、进程通信)。
核心特性:跨进程生效、支持区间细粒度锁、分为建议锁/强制锁。
2. 全套函数声明 & 参数详解
cpp
#include <fcntl.h>
#include <unistd.h>
int fcntl(int fd, int cmd, struct flock *lock);
参数解析:
-
fd:已打开的文件描述符; -
cmd:锁操作指令:-
F_SETLK:非阻塞加锁,失败直接返回; -
F_SETLKW:阻塞加锁,拿不到锁则等待;
-
-
lock:锁属性结构体:-
l_type:锁类型 F_RDLCK(读锁)、F_WRLCK(写锁)、F_UNLCK(解锁); -
l_start:文件锁定起始偏移; -
l_len:锁定区间长度,0 代表锁定到文件末尾。
-
3. 最小可运行代码示例
多进程安全写入日志文件,避免内容错乱:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
// 文件加写锁函数
void file_wlock(int fd) {
struct flock lock;
lock.l_type = F_WRLCK;
lock.l_start = 0;
lock.l_len = 0;
fcntl(fd, F_SETLKW, &lock);
}
// 文件解锁函数
void file_unlock(int fd) {
struct flock lock;
lock.l_type = F_UNLCK;
lock.l_start = 0;
lock.l_len = 0;
fcntl(fd, F_SETLK, &lock);
}
int main() {
int fd = open("log.txt", O_RDWR|O_CREAT|O_APPEND, 0666);
if (fd < 0) exit(-1);
// 模拟多进程写入
pid_t pid = fork();
if (pid == 0) {
// 子进程写入
file_wlock(fd);
write(fd, "子进程日志写入\n", strlen("子进程日志写入\n"));
sleep(1);
file_unlock(fd);
} else {
// 父进程写入
file_wlock(fd);
write(fd, "父进程日志写入\n", strlen("父进程日志写入\n"));
sleep(1);
file_unlock(fd);
}
close(fd);
return 0;
}
编译 :
gcc filelock_demo.c -o filelock_demo运行 :
./filelock_demo
4. 核心注意事项
-
默认是建议锁:仅遵守锁规则的进程生效,恶意进程可直接绕过锁修改文件;如需强制锁需开启文件特殊权限;
-
文件关闭自动解锁:进程退出、文件关闭后锁会自动释放,不适合长期持有锁的业务场景。
五、四大锁场景选型总结
| 同步机制 | 适用范围 | 核心优势 | 典型场景 |
|---|---|---|---|
| 互斥锁 | 多线程/跨进程 | 通用稳定、简单可靠 | 通用临界区、计数更新、状态修改 |
| 读写锁 | 多线程 | 读并发性能极高 | 配置读取、缓存查询、读多写少业务 |
| 条件变量 | 多线程 | 无空耗、精准时序同步 | 生产者消费者、线程等待唤醒 |
| 文件锁 | 多进程 | 跨进程生效、细粒度锁 | 多进程日志、配置文件读写 |
结尾
四大同步锁覆盖了 Linux 90% 以上的并发同步场景:线程互斥用互斥锁、读多写少用读写锁、时序等待用条件变量、跨进程文件同步用文件锁。
掌握函数原型+代码实战+避坑点,可以彻底解决开发中绝大多数竞态、死锁、性能问题,也是面试并发编程的核心考点。