Linux锁有哪几种?各自适用场景和底层原理是什么

来源:站长素材作者:台湾程序员头衔:程序员
导读:本期聚焦于小伙伴创作的《Linux锁有哪几种?各自适用场景和底层原理是什么》,敬请观看详情。排查多进程并发 bug 时,选错锁类型往往让系统吞吐直接腰斩。Linux 内核与用户态提供的锁并不只有互斥量一种,按阻塞行为可分成自旋锁、互斥锁、读写锁、信号量等类别。自旋锁在临界区极短时忙等不睡眠,适合中断上下文;互斥锁获取不到会休眠,降低 CPU 空转。读写锁区分读多写少场景,信号量则允许指定数量的持有者。弄清这些锁的底层实现差异与调度代价,才能在高并发服务里精准选型,避免死锁与性能陡降。

在 Linux 系统开发与运维中,并发控制离不开各种类型的锁。理解 Linux 锁有哪几种,以及它们背后的实现机制,是编写稳定高并发程序的基础。不同锁在是否睡眠、是否允许重入、读写偏好等方面存在本质差异,用错类型会导致性能下降甚至死锁。

Linux锁有哪几种?各自适用场景和底层原理是什么

一、自旋锁(spinlock)

自旋锁是一种典型的忙等待锁。当执行单元尝试获取自旋锁而锁已被占用时,它不会立刻进入睡眠状态,而是在循环中不断检查锁状态,直到锁被释放。这种机制决定了自旋锁适用于临界区代码非常短、且不能睡眠的上下文,例如中断处理程序或持有其他自旋锁的代码路径。

在 Linux 内核中,自旋锁通过原子操作实现。早期版本仅做简单忙等,后来引入了Ticket Spinlock以解决公平性问题和避免饥饿。用户态可通过 pthread_spinlock_t 使用类似逻辑。下面的代码展示了用户态自旋锁的基本用法:

#include <pthread.h>
#include <stdio.h>

pthread_spinlock_t lock;
int counter = 0;

void* worker(void* arg) {
    for (int i = 0; i < 100000; i++) {
        pthread_spin_lock(&lock);
        counter++;
        pthread_spin_unlock(&lock);
    }
    return NULL;
}

int main() {
    pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE);
    pthread_t t1, t2;
    pthread_create(&t1, NULL, worker, NULL);
    pthread_create(&t2, NULL, worker, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    printf("counter=%dn", counter);
    pthread_spin_destroy(&lock);
    return 0;
}

自旋锁的优点是不会触发上下文切换,因此在多核机器上锁占用时间极短时效率很高。缺点也明显:若临界区较长,忙等会白白消耗 CPU 周期,在单核系统上甚至可能造成死锁(因为持锁者无法被调度运行)。

此外,自旋锁通常不可递归获取。同一执行流第二次调用获取操作会永久自旋。内核态自旋锁还会在获取时关闭内核抢占,部分类型还会禁止本地中断,使用时必须严格遵循调用约束。

二、互斥锁(mutex)

互斥锁是最常见的睡眠锁。当线程无法获取互斥锁时,它会被挂起到等待队列,让出 CPU 给其他可运行任务,直到锁释放后被唤醒。这种机制适合临界区执行时间不确定或可能进入睡眠的用户态程序。

Linux 用户态的 pthread_mutex_t 在底层依赖 futex(快速用户态互斥量)机制。无竞争时仅在用户态完成原子修改,发生竞争才陷入内核。以下示例展示基本使用:

#include <pthread.h>
#include <stdio.h>

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
int shared = 0;

void* add(void* arg) {
    for (int i = 0; i < 50000; i++) {
        pthread_mutex_lock(&mtx);
        shared++;
        pthread_mutex_unlock(&mtx);
    }
    return NULL;
}

int main() {
    pthread_t a, b;
    pthread_create(&a, NULL, add, NULL);
    pthread_create(&b, NULL, add, NULL);
    pthread_join(a, NULL);
    pthread_join(b, NULL);
    printf("shared=%dn", shared);
    pthread_mutex_destroy(&mtx);
    return 0;
}

互斥锁相比自旋锁的最大优势是节省 CPU,但代价是上下文切换开销。若锁竞争频繁且临界区极小,频繁睡眠唤醒反而比自旋更慢。因此性能敏感场景要结合压测选择。

Linux 内核也提供 struct mutex,它与信号量不同,有严格的所有权概念,只能由持有者释放,且不支持递归(除非使用特定变种)。内核互斥锁还实现了乐观自旋,即在短等待期内先自旋再睡眠,兼顾两者优点。

三、读写锁(rwlock)

读写锁区分读操作与写操作。多个读者可同时持有读锁,但写者必须独占。它适合读多写少的数据结构,如配置缓存、路由表等。Linux 用户态对应 pthread_rwlock_t,内核态有 rwlock_t。

读写锁要注意写者饥饿问题:如果读者持续到来,写者可能长时间拿不到锁。下面代码展示读者写者模型:

#include <pthread.h>
#include <stdio.h>

pthread_rwlock_t rw;
int data = 0;

void* reader(void* arg) {
    pthread_rwlock_rdlock(&rw);
    printf("read data=%dn", data);
    pthread_rwlock_unlock(&rw);
    return NULL;
}

void* writer(void* arg) {
    pthread_rwlock_wrlock(&rw);
    data++;
    pthread_rwlock_unlock(&rw);
    return NULL;
}

int main() {
    pthread_rwlock_init(&rw, NULL);
    pthread_t r, w;
    pthread_create(&r, NULL, reader, NULL);
    pthread_create(&w, NULL, writer, NULL);
    pthread_join(r, NULL);
    pthread_join(w, NULL);
    pthread_rwlock_destroy(&rw);
    return 0;
}

读写锁在读者远多于写者时能显著提升并发度。但如果写操作频繁,其复杂性会带来额外开销,且内核态 rwlock_t 本质是自旋型读写锁,使用时同样不能睡眠。

在实际服务开发中,若写冲突不高,也可考虑 RCU(读复制更新)机制替代读写锁。RCU 让读者完全无锁,写者复制新版本再批量回收,是 Linux 内核中高性能读场景的重要方案。

四、信号量(semaphore)

信号量是一个更通用的同步原语,它维护一个计数值,允许最多 N 个执行单元同时进入临界区。二值信号量类似互斥锁,但信号量没有严格的所有权,任何单元都可执行释放操作,且可能支持跨进程。

在 Linux 内核中,信号量用 struct semaphore 表示,down 和 up 分别对应减和加。用户态可使用无名信号量 sem_t。示例如下:

#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>

sem_t sem;
int val = 0;

void* task(void* arg) {
    sem_wait(&sem);
    val++;
    printf("val=%dn", val);
    sem_post(&sem);
    return NULL;
}

int main() {
    sem_init(&sem, 0, 1);
    pthread_t t1, t2;
    pthread_create(&t1, NULL, task, NULL);
    pthread_create(&t2, NULL, task, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    sem_destroy(&sem);
    return 0;
}

信号量的灵活性让它可用于生产者消费者模型、资源池限制等。但它比互斥锁更重,且缺乏所有权导致调试困难,用户态新代码多推荐使用互斥锁加条件变量替代。

内核信号量在现代代码中已不鼓励使用,官方更推荐 mutex 或完成量(completion)。不过理解信号量有助于阅读旧驱动与底层同步理论。

五、其他常见锁与机制

除了上述四类,Linux 还提供多种专项机制。比如原子变量(atomic_t)通过 CPU 原子指令实现无锁计数;顺序锁(seqlock)让写者独占、读者重试,适合极少写极多读;RCU 如前文所述;以及 percpu 变量通过避免共享来消除锁。

从选型角度看,单核禁止自旋忙等,中断上下文只能用自旋类锁,用户态长临界区优先互斥锁,读多写少评估读写锁或 RCU。清楚这些锁的差异,才能在排查竞争与优化吞吐时做出正确判断。

锁类型睡眠与否典型场景
自旋锁忙等不睡眠中断、极短临界区
互斥锁睡眠用户态通用临界区
读写锁读共享写独占读多写少
信号量可睡眠资源计数、旧内核

综合来看,Linux 锁的种类虽多,但核心权衡始终是上下文切换成本、并发粒度与正确性。掌握它们才能让系统既安全又高效。

linux_lockmutexspinlock修改时间:2026-08-01 03:30:37

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。