Linux 四大进程/线程同步锁详解:互斥锁、读写锁、条件变量、文件锁

前言

在 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% 以上的并发同步场景:线程互斥用互斥锁、读多写少用读写锁、时序等待用条件变量、跨进程文件同步用文件锁。

掌握函数原型+代码实战+避坑点,可以彻底解决开发中绝大多数竞态、死锁、性能问题,也是面试并发编程的核心考点。

相关推荐
IT_陈寒6 小时前
Vite动态导入把我坑惨了,原来要这样用才对
前端·人工智能·后端
L、2186 小时前
CANN调优工具链全景:从profiler到tensorboard的完整观测体系
linux·运维·服务器·深度学习
社交怪人7 小时前
【数字对调】信息学奥赛一本通C语言解法(题号2070)
c语言·开发语言
hef2887 小时前
C语言中char指针与数组的区别及应用
c语言·开发语言
j_xxx404_7 小时前
Linux进程信号捕捉与操作系统运行本质深度解析
linux·运维·服务器·开发语言·c++·人工智能·ai
eggrall7 小时前
Linux信号——保存信号
linux·运维·服务器
2501_920047037 小时前
firewalld的使用
linux·运维
z202305087 小时前
以太网之VLAN介绍
linux·服务器·网络·人工智能·ai
__Benco7 小时前
创建一个 Linux5.10 普通 kill 无效的守护进程 Daemon-demo
c语言