首页 电商直播

Linux 线程同步互斥深度解析:锁、信号量与条件变量实战避坑

分类:电商直播
字数: (9033)
阅读: (4114)
内容摘要:Linux 线程同步互斥深度解析:锁、信号量与条件变量实战避坑,

在多线程编程中,线程同步和互斥是至关重要的概念,用于解决多个线程并发访问共享资源时可能出现的数据不一致问题。上一篇文章我们讨论了基本的概念和一些简单的锁机制,本文将继续深入探讨更高级的同步互斥机制,并结合实际案例分析常见的陷阱和解决方案。尤其是在高并发的场景下,例如 Nginx 反向代理服务器处理大量并发连接请求时,对共享内存的访问就需要非常精细的控制,否则可能导致数据错乱甚至服务崩溃。

互斥锁(Mutex)的进阶使用

死锁的预防与避免

死锁是使用互斥锁时最常见的陷阱之一。当多个线程相互等待对方释放锁时,就会发生死锁,导致所有线程都无法继续执行。以下是一些预防和避免死锁的方法:

Linux 线程同步互斥深度解析:锁、信号量与条件变量实战避坑
  • 避免循环等待: 确保线程按照固定的顺序获取锁。如果线程需要同时获取多个锁,则应该按照相同的顺序获取,避免形成循环等待的局面。
  • 使用pthread_mutex_trylock() 这个函数尝试获取锁,如果锁已经被其他线程持有,则立即返回错误,而不是阻塞等待。线程可以根据返回值来决定是否继续尝试获取锁或执行其他操作。
  • 超时机制: 可以设置锁的超时时间,如果线程在指定的时间内无法获取锁,则放弃获取,释放已经持有的锁,避免长时间的阻塞。
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

pthread_mutex_t mutex1, mutex2;

void *thread_func1(void *arg) {
    printf("线程1: 尝试获取 mutex1\n");
    pthread_mutex_lock(&mutex1); // 获取 mutex1
    printf("线程1: 成功获取 mutex1\n");

    // 模拟一些操作
    sleep(1);

    printf("线程1: 尝试获取 mutex2\n");
    int result = pthread_mutex_trylock(&mutex2); // 尝试获取 mutex2
    if (result == 0) {
        printf("线程1: 成功获取 mutex2\n");
        pthread_mutex_unlock(&mutex2); // 释放 mutex2
    } else if (result == EBUSY) {
        printf("线程1: mutex2 繁忙,放弃获取\n");
    } else {
        perror("线程1: pthread_mutex_trylock");
    }

    pthread_mutex_unlock(&mutex1); // 释放 mutex1
    printf("线程1: 释放 mutex1\n");

    pthread_exit(NULL);
}

void *thread_func2(void *arg) {
    printf("线程2: 尝试获取 mutex2\n");
    pthread_mutex_lock(&mutex2); // 获取 mutex2
    printf("线程2: 成功获取 mutex2\n");

    // 模拟一些操作
    sleep(1);

    printf("线程2: 尝试获取 mutex1\n");
    pthread_mutex_lock(&mutex1); // 获取 mutex1
    printf("线程2: 成功获取 mutex1\n");

    pthread_mutex_unlock(&mutex1); // 释放 mutex1
    printf("线程2: 释放 mutex1\n");
    pthread_mutex_unlock(&mutex2); // 释放 mutex2
    printf("线程2: 释放 mutex2\n");

    pthread_exit(NULL);
}

int main() {
    pthread_t thread1, thread2;

    pthread_mutex_init(&mutex1, NULL);
    pthread_mutex_init(&mutex2, NULL);

    pthread_create(&thread1, NULL, thread_func1, NULL);
    pthread_create(&thread2, NULL, thread_func2, NULL);

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    pthread_mutex_destroy(&mutex1);
    pthread_mutex_destroy(&mutex2);

    return 0;
}

读写锁(Read-Write Locks)

读写锁允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。这在读操作远多于写操作的场景下非常有用,可以提高并发性能。例如,Nginx 的配置信息经常被读取,但很少被修改,使用读写锁可以显著提高性能。

Linux 线程同步互斥深度解析:锁、信号量与条件变量实战避坑
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

pthread_rwlock_t rwlock;
int shared_data = 0;

void *reader_thread(void *arg) {
    for (int i = 0; i < 5; i++) {
        pthread_rwlock_rdlock(&rwlock); // 获取读锁
        printf("Reader: Shared data = %d\n", shared_data);
        pthread_rwlock_unlock(&rwlock); // 释放读锁
        sleep(1);
    }
    pthread_exit(NULL);
}

void *writer_thread(void *arg) {
    for (int i = 0; i < 3; i++) {
        pthread_rwlock_wrlock(&rwlock); // 获取写锁
        shared_data++;
        printf("Writer: Updated shared data to %d\n", shared_data);
        pthread_rwlock_unlock(&rwlock); // 释放写锁
        sleep(2);
    }
    pthread_exit(NULL);
}

int main() {
    pthread_t reader1, reader2, writer;

    pthread_rwlock_init(&rwlock, NULL);

    pthread_create(&reader1, NULL, reader_thread, NULL);
    pthread_create(&reader2, NULL, reader_thread, NULL);
    pthread_create(&writer, NULL, writer_thread, NULL);

    pthread_join(reader1, NULL);
    pthread_join(reader2, NULL);
    pthread_join(writer, NULL);

    pthread_rwlock_destroy(&rwlock);

    return 0;
}

条件变量(Condition Variables)

条件变量用于线程间的通知和等待。一个线程可以等待某个条件成立,而另一个线程可以在条件成立时通知等待的线程。这在生产者-消费者模型中非常有用。

Linux 线程同步互斥深度解析:锁、信号量与条件变量实战避坑
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

pthread_mutex_t mutex;
pthread_cond_t cond;
int data_ready = 0;

void *consumer_thread(void *arg) {
    pthread_mutex_lock(&mutex); // 获取互斥锁
    while (!data_ready) {
        printf("Consumer: 等待数据...\n");
        pthread_cond_wait(&cond, &mutex); // 等待条件变量
    }
    printf("Consumer: 接收到数据\n");
    pthread_mutex_unlock(&mutex); // 释放互斥锁
    pthread_exit(NULL);
}

void *producer_thread(void *arg) {
    sleep(2);
    pthread_mutex_lock(&mutex); // 获取互斥锁
    data_ready = 1;
    printf("Producer: 数据已准备好\n");
    pthread_cond_signal(&cond); // 发送信号通知等待线程
    pthread_mutex_unlock(&mutex); // 释放互斥锁
    pthread_exit(NULL);
}

int main() {
    pthread_t consumer, producer;

    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);

    pthread_create(&consumer, NULL, consumer_thread, NULL);
    pthread_create(&producer, NULL, producer_thread, NULL);

    pthread_join(consumer, NULL);
    pthread_join(producer, NULL);

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);

    return 0;
}

条件变量的虚假唤醒

需要注意的是,条件变量可能会出现虚假唤醒(spurious wakeup),即线程在没有收到通知的情况下被唤醒。因此,在等待条件变量时,应该始终使用 while 循环来检查条件是否真的成立。

Linux 线程同步互斥深度解析:锁、信号量与条件变量实战避坑

信号量(Semaphores)

信号量是一种计数器,用于控制对共享资源的访问。信号量可以用于实现互斥锁和条件变量的功能,但它更通用。

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

#define BUFFER_SIZE 5

int buffer[BUFFER_SIZE];
int in = 0;
int out = 0;

sem_t empty; // 信号量,表示空闲缓冲区数量
sem_t full;  // 信号量,表示已填充缓冲区数量
pthread_mutex_t mutex;

void *producer(void *arg) {
    int item;
    for (int i = 0; i < 10; i++) {
        item = rand() % 100; // 生产一个随机数
        sem_wait(&empty);      // 等待空闲缓冲区
        pthread_mutex_lock(&mutex); // 获取互斥锁
        buffer[in] = item;
        printf("Producer produced item: %d\n", item);
        in = (in + 1) % BUFFER_SIZE;
        pthread_mutex_unlock(&mutex); // 释放互斥锁
        sem_post(&full);       // 增加已填充缓冲区数量
        sleep(1);
    }
    pthread_exit(NULL);
}

void *consumer(void *arg) {
    int item;
    for (int i = 0; i < 10; i++) {
        sem_wait(&full);       // 等待已填充缓冲区
        pthread_mutex_lock(&mutex); // 获取互斥锁
        item = buffer[out];
        printf("Consumer consumed item: %d\n", item);
        out = (out + 1) % BUFFER_SIZE;
        pthread_mutex_unlock(&mutex); // 释放互斥锁
        sem_post(&empty);      // 增加空闲缓冲区数量
        sleep(2);
    }
    pthread_exit(NULL);
}

int main() {
    pthread_t producer_thread, consumer_thread;

    sem_init(&empty, 0, BUFFER_SIZE); // 初始化 empty 信号量为缓冲区大小
    sem_init(&full, 0, 0);            // 初始化 full 信号量为 0
    pthread_mutex_init(&mutex, NULL);

    pthread_create(&producer_thread, NULL, producer, NULL);
    pthread_create(&consumer_thread, NULL, consumer, NULL);

    pthread_join(producer_thread, NULL);
    pthread_join(consumer_thread, NULL);

    sem_destroy(&empty);
    sem_destroy(&full);
    pthread_mutex_destroy(&mutex);

    return 0;
}

实战避坑经验

  • 避免长时间持有锁: 长时间持有锁会降低并发性能,甚至导致其他线程饥饿。应该尽量减少锁的持有时间,只在必要的时候获取锁。例如,在高并发的宝塔面板服务器上,如果数据库操作占用了过多的锁时间,就会影响用户的响应速度。
  • 使用合适的同步机制: 不同的同步机制适用于不同的场景。例如,读写锁适用于读多写少的场景,信号量适用于资源计数场景。
  • 仔细测试: 并发编程容易出现难以调试的错误。应该编写充分的测试用例,覆盖各种边界情况和异常情况,确保程序的正确性。
  • 使用工具进行分析: 可以使用 Valgrind 等工具来检测内存泄漏和数据竞争等问题。

希望本文能够帮助你更好地理解 Linux 下的线程同步和互斥机制,并在实际开发中避免常见的陷阱。

Linux 线程同步互斥深度解析:锁、信号量与条件变量实战避坑

转载请注明出处: 键盘上的咸鱼

本文的链接地址: http://m.acea2.store/blog/476255.SHTML

本文最后 发布于2026-04-02 22:20:02,已经过了25天没有更新,若内容或图片 失效,请留言反馈

()
您可能对以下文章感兴趣
评论
  • 螺蛳粉真香 5 天前
    条件变量的虚假唤醒是个大坑啊,以前没注意,多谢提醒!
  • 陕西油泼面 4 天前
    请问作者,nginx配置信息修改的时候,用什么锁来保证原子性呢?
  • 冬天里的一把火 2 天前
    信号量这块例子很清晰,生产者消费者模型一下子就理解了。