Linux多线程同步机制--互斥锁(mutex)

原创 Linux二进制 2024-06-28 08:18

引言

在Linux多线程编程中,互斥锁(Mutex)是一种非常重要的同步机制,用于控制对共享资源的访问,确保在任意时刻只有一个线程可以访问特定的资源或代码段,即临界区。互斥锁的主要用途是防止多个线程同时访问共享资源,从而避免竞争条件和数据不一致的问题

互斥锁的工作原理相对简单,它通过锁定和解锁操作来控制对共享资源的访问。当一个线程需要访问共享资源时,它首先尝试锁定互斥锁。如果互斥锁已经被其他线程锁定,请求线程将被阻塞,直到互斥锁被解锁。互斥锁的锁定和解锁操作必须是成对出现的,以确保对共享资源的正确访问。

一、互斥锁原型

在 Linux 中,互斥锁通常通过 POSIX 线程库(pthread)来实现。pthread 库提供了一系列的函数来创建、初始化、锁定、解锁和销毁互斥锁,如pthread_mutex_init()pthread_mutex_lock()pthread_mutex_unlock()pthread_mutex_destroy()等。互斥锁的初始化是通过pthread_mutex_init()函数完成的,该函数会分配必要的资源来创建一个互斥锁,并将其初始化为未锁定状态。

pthread_mutex_t 是 POSIX 线程库中用于互斥锁的数据类型。以下是一些与 pthread_mutex_t 相关的函数原型:

  1. 初始化互斥锁

    int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
  • mutex:指向 pthread_mutex_t 结构的指针,用于初始化互斥锁。
  • attr:指向 pthread_mutexattr_t 结构的指针,包含互斥锁的属性。如果为 NULL,则使用默认属性。
  • 销毁互斥锁

    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    • mutex:指向已经初始化的 pthread_mutex_t 结构的指针。销毁互斥锁前,确保互斥锁没有被锁定。
  • 锁定互斥锁

    int pthread_mutex_lock(pthread_mutex_t *mutex);
    • mutex:指向已经初始化的 pthread_mutex_t 结构的指针。如果互斥锁已经被其他线程锁定,则调用线程将被阻塞,直到互斥锁被解锁。
  • 尝试锁定互斥锁(非阻塞):

    int pthread_mutex_trylock(pthread_mutex_t *mutex);
    • mutex:指向已经初始化的 pthread_mutex_t 结构的指针。如果互斥锁已经被锁定,则立即返回错误码 EBUSY,而不是等待互斥锁变为可用。
  • 定时锁定互斥锁(可指定超时时间):

    int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
    • mutex:指向已经初始化的 pthread_mutex_t 结构的指针。
    • abstime:指向 timespec 结构的指针,包含超时时间。如果超时时间到达,互斥锁仍未解锁,则返回 ETIMEDOUT
  • 解锁互斥锁

    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    • mutex:指向已经初始化且当前被调用线程锁定的 pthread_mutex_t 结构的指针。调用此函数将释放互斥锁,允许其他线程锁定它。

    注意:互斥锁的初始化方式主要有两种:静态初始化和动态初始化。

    1. 静态初始化: 使用宏 PTHREAD_MUTEX_INITIALIZER 可以在声明互斥锁变量时直接初始化。这种方式是编译时初始化,无需调用初始化函数。例如:

      pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

      这种方式适用于静态分配的互斥锁,即在程序的整个生命周期内都存在的锁。

    2. 动态初始化: 使用 pthread_mutex_init() 函数可以在程序运行时初始化互斥锁。这种方式需要显式调用函数进行初始化和销毁,适用于需要动态创建和销毁的互斥锁。例如:

      pthread_mutex_t *mutex = malloc(sizeof(pthread_mutex_t));
      pthread_mutex_init(mutex, NULL); // NULL 表示使用默认属性

      使用动态初始化时,需要在不再需要互斥锁时调用 pthread_mutex_destroy() 来销毁它,并释放分配的内存。

    互斥锁的属性可以在初始化时指定,如果不设置属性(即使用 NULL 或默认属性),则使用系统默认的互斥锁属性。在Linux系统中,属性可以用来定义不同类型的互斥锁,例如:

    • 普通互斥锁(PTHREAD_MUTEX_TIMED_NP
    • 递归互斥锁(PTHREAD_MUTEX_RECURSIVE_NP
    • 错误检查互斥锁(PTHREAD_MUTEX_ERRORCHECK_NP
    • 适应性互斥锁(PTHREAD_MUTEX_ADAPTIVE_NP

    正确初始化互斥锁对于避免潜在的同步问题是非常重要的。使用静态初始化可以简化代码并减少出错机会,而动态初始化提供了更大的灵活性。在实际编程中,应根据具体需求选择适合的初始化方式。

    这些函数是 POSIX 线程库中用于线程同步的基础,通过它们可以安全地在多线程程序中控制对共享资源的访问。

    三、互斥锁特性

    互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁( lock )和解锁(unlock),如果互斥量已经上锁,调用线程会阻塞,直到互斥量被解锁。在完成了对共享资源的访问后,要对互斥量进行解锁。

    互斥锁特点如下:

    1. 原子性:把一个互斥量锁定为一个原子操作,操作系统保证了如果一个线程锁定了一个互斥量,没有其他线程在同一时间可以再成功锁定这个互斥量;
    2. 唯一性:如果一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量;
    3. 非忙等待:如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何 cpu 资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥量。

    四、互斥锁加锁阻塞

    互斥锁是一种【独占锁】,比如当线程 A 加锁成功后,此时互斥锁已经被线程 A 独占了,只要线程 A 没有释放手中的锁,线程 B 加锁就会失败,于是就会释放 CPU 让给其他线程,既然线程 B 释放掉了 CPU,自然线程 B 加锁的代码就会被阻塞

    对于互斥锁加锁失败而阻塞的现象,是由操作系统内核实现的。当加锁失败时,内核会将线程置为【睡眠】状态,等到锁被释放后,内核会在合适的时机唤醒线程,当这个线程成功获取到锁后,于是就可以继续执行。如下图:

    所以,互斥锁加锁失败时,会从用户态陷入到内核态,让内核帮我们切换线程,虽然简化了使用锁的难度,但是存在一定的性能开销成本。

    那这个开销成本是什么呢?会有两次线程上下文切换的成本

    • 当线程加锁失败时,内核会把线程的状态从【运行】状态设置为【睡眠】状态,然后把 CPU 切换给其他线程运行;

    • 接着,当锁被释放时,之前【睡眠】状态的线程会变为【就绪】状态,然后内核会在合适的时间,把 CPU 切换给该线程运行。

    线程的上下文切换的是什么?当两个线程是属于同一个进程,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据。

    上下切换的耗时有大佬统计过,大概在几十纳秒到几微秒之间,如果你锁住的代码执行时间比较短,那可能上下文切换的时间都比你锁住的代码执行时间还要长。

    所以,如果你能确定被锁住的代码执行时间很短,就不应该用互斥锁,而应该选用自旋锁,否则使用互斥锁。

    五、互斥锁死锁

    互斥锁导致的死锁是多线程编程中的一个常见问题。死锁发生时,两个或多个线程被无限期地阻塞,因为它们在等待对方释放锁。以下是一些关于互斥锁死锁的信息:

    1. 死锁的条件:死锁通常发生在以下四个条件同时满足时:

    • 互斥:资源不能被多个线程同时访问。
    • 占有和等待:一个线程持有至少一个锁,并且等待获取其他线程持有的锁。
    • 不可剥夺:已经获得的锁不能被其他线程强行剥夺,只能由持有它的线程释放。
    • 循环等待:存在一个线程持有锁的循环链,每个线程都在等待下一个线程持有的锁。
  • 死锁的避免:可以通过以下几种策略来避免死锁:

    • 固定顺序加锁:总是以相同的顺序获取多个锁。
    • 超时尝试加锁:使用 pthread_mutex_trylock() 或其他带有超时功能的加锁方法。
    • 一次性获取所有锁:在开始访问共享资源前,先获取所有需要的锁。
    • 使用死锁检测算法:定期检测死锁情况,并采取措施解决。
  • 死锁的解除:如果检测到死锁,可以采取以下措施:

    • 剥夺资源:从其他线程剥夺资源,赋予死锁线程。
    • 撤销进程:终止一个或多个线程或进程,打破死锁状态。
    • 资源重分配:重新分配资源,以打破循环等待条件。
  • 避免嵌套锁:尽量减少锁的嵌套使用,如果必须嵌套使用,确保内层锁总是不同类型的或者使用不同的加锁顺序。

  • 锁的分级管理:将锁分级,高级别的锁可以包含多个低级别的锁,确保在请求高级别锁时,已经持有所有需要的低级别锁。

  • 通过采取这些措施,可以降低死锁发生的风险,并提高多线程程序的稳定性和可靠性。

    六、互斥锁实战

    1、互斥锁加解锁

    以下是一个简单的示例代码,展示了如何在多线程环境中使用 pthread_mutex_t 来同步对同一个文件的读写操作:

    #include 
    #include
    #include

    #define NUM_THREADS 5 // 定义宏来设置线程数量

    pthread_mutex_t file_mutex = PTHREAD_MUTEX_INITIALIZER; // 全局互斥锁

    const char *data_to_write = "Thread data\n";

    void perform_file_write(const char *filename) {
    FILE *fp = fopen(filename, "a");
    if (fp == NULL) {
    perror("Error opening file for write");
    return;
    }

    pthread_mutex_lock(&file_mutex); // 加锁
    fputs(data_to_write, fp);
    pthread_mutex_unlock(&file_mutex); // 解锁
    fclose(fp);
    }

    void perform_file_read(const char *filename) {
    FILE *fp = fopen(filename, "r");
    if (fp == NULL) {
    perror("Error opening file for read");
    return;
    }

    char buffer[256];
    pthread_mutex_lock(&file_mutex); // 加锁
    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
    printf("%s", buffer); // 打印读取的数据
    }
    pthread_mutex_unlock(&file_mutex); // 解锁
    fclose(fp);
    }

    void *thread_function(void *arg) {
    int is_write = *(int *)arg; // 根据传入的参数决定操作类型
    const char *filename = "example.txt"; // 要操作的文件名

    if (is_write) {
    perform_file_write(filename);
    } else {
    perform_file_read(filename);
    }
    return NULL;
    }

    int main() {
    int *args = malloc(NUM_THREADS * sizeof(int)); // 动态分配参数数组
    if (args == NULL) {
    perror("Failed to allocate memory for args");
    return 1;
    }

    pthread_t threads[NUM_THREADS];
    for (int i = 0; i < NUM_THREADS; ++i) {
    args[i] = (i == 0); // 第一个线程执行写操作,其余执行读操作
    if (pthread_create(&threads[i], NULL, thread_function, &args[i]) != 0) {
    perror("Failed to create thread");
    free(args);
    return 1;
    }
    }

    for (int i = 0; i < NUM_THREADS; ++i) {
    pthread_join(threads[i], NULL);
    }

    free(args); // 释放参数数组
    // 静态初始化的互斥锁不需要显式销毁
    return 0;
    }

    在这个示例中,创建了一个互斥锁 file_mutex 来同步对文件 example.txt 的访问。使用 PTHREAD_MUTEX_INITIALIZER 直接初始化互斥锁,无需再调用pthread_mutex_init 函数了。我们定义了 perform_file_read 和 perform_file_write 函数来执行实际的文件读写操作,并在这些操作前后使用 pthread_mutex_lock 和 pthread_mutex_unlock 来确保每次只有一个线程可以访问文件。在 main 函数中,创建了一个线程数组,并设置第一个线程执行写操作,其余线程执行读操作。使用 perror 来打印出创建线程或文件操作失败时的错误信息。最后,我们在主函数中等待所有线程完成,并且由于使用了静态初始化互斥锁,我们不需要调用 pthread_mutex_destroy 来销毁互斥锁。

    程序运行结果如下:

    [root@localhost multi_pthread_file]# ./multi_pthread_file_rw
    Thread data
    Thread data
    Thread data
    Thread data
    [root@localhost multi_pthread_file]# cat example.txt
    Thread data

    通过程序运行结果可知,我们通过给写操作和读操作加互斥锁,成功实现我们预期的目标,即写一次数据,读四次数据。

    2、互斥锁死锁

    以下是一个 C 语言中可能导致死锁的互斥锁代码示例:

    #include 
    #include
    #include

    pthread_mutex_t mutex1;
    pthread_mutex_t mutex2;

    void *thread1Function(void *arg) {
    pthread_mutex_lock(&mutex1);
    printf("Thread 1 acquired mutex1\n");

    sleep(1);

    pthread_mutex_lock(&mutex2);
    printf("Thread 1 acquired mutex2\n");

    pthread_mutex_unlock(&mutex2);
    pthread_mutex_unlock(&mutex1);

    return NULL;
    }

    void *thread2Function(void *arg) {
    pthread_mutex_lock(&mutex2);
    printf("Thread 2 acquired mutex2\n");

    sleep(1);

    pthread_mutex_lock(&mutex1);
    printf("Thread 2 acquired mutex1\n");

    pthread_mutex_unlock(&mutex1);
    pthread_mutex_unlock(&mutex2);

    return NULL;
    }

    int main() {
    pthread_t thread1, thread2;

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

    pthread_create(&thread1, NULL, thread1Function, NULL);
    pthread_create(&thread2, NULL, thread2Function, NULL);

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

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

    return 0;
    }

    在这个示例中,thread1 先获取 mutex1 然后尝试获取 mutex2,而 thread2 先获取 mutex2 然后尝试获取 mutex1,可能会导致两个线程相互等待对方释放锁,从而造成死锁。

    拓展】上文中 sleep(1) 的作用:

    在上述代码中,两个线程函数中设置 sleep(1) 的主要目的是增加死锁发生的可能性。当线程获取一个互斥锁后,通过 sleep(1) 让线程暂停一段时间,使得另一个线程有机会去获取另一个互斥锁,从而更有可能形成两个线程相互等待对方持有的锁的情况,导致死锁的发生。

    如果没有这个 sleep(1) ,由于线程执行速度非常快,可能在一个线程完成对两个锁的获取和操作之前,另一个线程还没有机会执行获取锁的操作,这样死锁就不太容易出现,不利于演示和观察死锁的情况。

    通过添加 sleep(1) ,模拟了线程操作中的一定延迟,使得线程之间的竞争和等待更加明显,更有可能展示出死锁的现象。

    编译并执行可执行程序如下:

    [root@localhost multi_pthread_file]# ./dead_lock
    Thread 1 acquired mutex1
    Thread 2 acquired mutex2

    执行可执行程序 dead_lock ,发现该进程在输出两条信息后卡住不动,无法继续执行,出现停滞或长时间无响应的情况,由此推断该进程死锁了。

    使用 gdb 命令附加到可能发生死锁的进程。可以通过 ps 命令找到进程 IDPID),然后使用 gdb 加上进程 ID 来附加到该进程。例如:

    [root@localhost multi_pthread_file]# gdb -p 251640 -q
    Attaching to process 251640
    [New LWP 251641]
    [New LWP 251642]
    [Thread debugging using libthread_db enabled]
    Using host libthread_db library "/lib64/libthread_db.so.1".
    0x00007fb09e6906cd in __pthread_timedjoin_ex () from /lib64/libpthread.so.0
    Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-251.el8.x86_64
    (gdb) info threads
    Id Target Id Frame
    * 1 Thread 0x7fb09eac8740 (LWP 251640) "dead_lock" 0x00007fb09e6906cd in __pthread_timedjoin_ex () from /lib64/libpthread.so.0
    2 Thread 0x7fb09e2b0700 (LWP 251641) "dead_lock" 0x00007fb09e69885d in __lll_lock_wait () from /lib64/libpthread.so.0
    3 Thread 0x7fb09daaf700 (LWP 251642) "dead_lock" 0x00007fb09e69885d in __lll_lock_wait () from /lib64/libpthread.so.0
    (gdb)

    使用 info threads 命令列出进程中的所有线程。这将显示每个线程的 ID 和当前状态。死锁的线程通常会显示为在等待锁(例如,在 __lll_lock_wait)。根据上述结果可知,该进程已死锁。

    3、互斥锁死锁检测和恢复

    以下是一个使用 C 语言实现互斥锁、死锁检测和恢复的简单示例代码:

    #include 
    #include
    #include
    #include
    #include

    pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER;
    pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;

    int lock1_held = 0;
    int lock2_held = 0;

    void *thread1(void *arg) {
    pthread_mutex_lock(&lock1);
    printf("Thread 1 acquired lock 1\n");
    lock1_held = 1;

    sleep(1); // 模拟耗时操作

    while (lock2_held && lock1_held) { // 持续检测死锁条件
    printf("Thread 1 detects potential deadlock and releases lock 1\n");
    pthread_mutex_unlock(&lock1);
    lock1_held = 0;
    sleep(1); // 等待一段时间后再次尝试
    pthread_mutex_lock(&lock1);
    lock1_held = 1;
    }

    pthread_mutex_lock(&lock2);
    printf("Thread 1 acquired lock 2\n");

    pthread_mutex_unlock(&lock2);
    pthread_mutex_unlock(&lock1);
    lock1_held = 0;
    lock2_held = 0;

    return NULL;
    }

    void *thread2(void *arg) {
    pthread_mutex_lock(&lock2);
    printf("Thread 2 acquired lock 2\n");
    lock2_held = 1;

    sleep(1); // 模拟耗时操作

    while (lock1_held && lock2_held) { // 持续检测死锁条件
    printf("Thread 2 detects potential deadlock and releases lock 2\n");
    pthread_mutex_unlock(&lock2);
    lock2_held = 0;
    sleep(1); // 等待一段时间后再次尝试
    pthread_mutex_lock(&lock2);
    lock2_held = 1;
    }

    pthread_mutex_lock(&lock1);
    printf("Thread 2 acquired lock 1\n");

    pthread_mutex_unlock(&lock1);
    pthread_mutex_unlock(&lock2);
    lock1_held = 0;
    lock2_held = 0;

    return NULL;
    }

    int main() {
    pthread_t t1, t2;

    pthread_create(&t1, NULL, thread1, NULL);
    pthread_create(&t2, NULL, thread2, NULL);

    sleep(5); // 等待一段时间,让死锁有机会发生

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    return 0;
    }

    在上述示例中,我们进行了如下操作:

    1. 定义了两个互斥锁 lock1 和 lock2,以及两个标志 lock1_held 和 lock2_held 来跟踪锁的持有状态。

    2. thread1 函数:

    • 首先获取 lock1 并设置相应的标志。
    • 经过一段模拟耗时操作后,进入一个 while 循环,持续检测是否同时持有 lock1 和 lock2 导致死锁。
    • 如果检测到死锁,打印提示信息,释放 lock1,将标志重置,等待一段时间后重新获取 lock1 并再次设置标志。
    • 如果没有死锁,获取 lock2,完成操作后释放两个锁并重置标志。
  • thread2 函数:

    • 与 thread1 函数类似,首先获取 lock2 并设置标志。
    • 经过模拟耗时操作后,在 while 循环中检测死锁情况并进行相应处理。
    • 最终获取 lock1,完成操作后释放锁和重置标志。
  • main 函数:

    • 创建两个线程分别执行 thread1 和 thread2 函数。
    • 等待一段时间,让死锁有机会发生。
    • 等待两个线程结束。
    [root@localhost multi_pthread_file]# ./dead_lock_detect
    Thread 1 acquired lock 1
    Thread 2 acquired lock 2
    Thread 1 detects potential deadlock and releases lock 1
    Thread 2 detects potential deadlock and releases lock 2
    Thread 1 acquired lock 2
    Thread 2 acquired lock 1

    通过这种方式,每个线程在获取第二个锁之前,持续检测死锁情况,并在可能死锁时采取释放已持有的锁、等待后重新尝试获取的策略,以尽量避免死锁的发生。但需要注意的是,这仍然不是一种完全可靠的死锁避免机制,在复杂的多线程环境中,可能需要更完善的同步和协调策略。

    4、固定顺序加锁避免死锁

    以下是一个使用 C 语言实现互斥锁通过固定加锁顺序来避免死锁的发生简单示例代码:

    #include 
    #include
    #include

    // 互斥锁
    pthread_mutex_t mutex1;
    pthread_mutex_t mutex2;

    // 死锁检测标志
    int deadlockDetected = 0;

    void *thread1Function(void *arg) {
    pthread_mutex_lock(&mutex1);
    printf("Thread 1 acquired mutex1\n");

    sleep(1);

    pthread_mutex_lock(&mutex2);
    printf("Thread 1 acquired mutex2\n");

    pthread_mutex_unlock(&mutex2);
    pthread_mutex_unlock(&mutex1);

    return NULL;
    }

    void *thread2Function(void *arg) {
    pthread_mutex_lock(&mutex1);
    printf("Thread 2 acquired mutex1\n");

    sleep(1);

    pthread_mutex_lock(&mutex2);
    printf("Thread 2 acquired mutex2\n");

    pthread_mutex_unlock(&mutex2);
    pthread_mutex_unlock(&mutex1);

    return NULL;
    }

    // 模拟死锁检测
    void detectDeadlock() {
    sleep(3);
    // 由于固定了加锁顺序,这里不会发生死锁,无需检测
    }

    int main() {
    pthread_t thread1, thread2;

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

    pthread_create(&thread1, NULL, thread1Function, NULL);
    pthread_create(&thread2, NULL, thread2Function, NULL);

    detectDeadlock();

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

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

    return 0;
    }

    在上述示例中,我们进行了如下操作:

    1. 首先,定义了两个互斥锁 mutex1 和 mutex2,以及一个标志 deadlockDetected 用于死锁检测(但在当前代码中未实际使用)。
    2. thread1Function 和 thread2Function 分别是两个线程的执行函数。
    • thread1Function 先获取 mutex1,等待 1 秒后再获取 mutex2,最后释放两个锁。
    • thread2Function 逻辑相同,也是先获取 mutex1,等待 1 秒后获取 mutex2,最后释放。
  • detectDeadlock 函数用于模拟死锁检测,但由于当前固定了加锁顺序,实际上不会发生死锁,所以此函数内未进行真正的检测操作。
  • 在 main 函数中:
    • 初始化两个互斥锁。
    • 创建两个线程分别执行 thread1Function 和 thread2Function 。
    • 调用 detectDeadlock 函数进行死锁检测(但如前所述,此处在当前代码中未实际生效)。
    • 使用 pthread_join 等待两个线程结束。
    • 最后销毁两个互斥锁。 总的来说,这段代码创建了两个线程并尝试获取两个互斥锁,但由于固定的加锁顺序,不会产生死锁。

    程序运行结果如下:

    [root@localhost multi_pthread_file]# ./dead_lock_detect_fixed_mutex
    Thread 1 acquired mutex1
    Thread 1 acquired mutex2
    Thread 2 acquired mutex1
    Thread 2 acquired mutex2

    通过程序运行结果可知,多个线程使用固定的加锁顺序,不会产生死锁。

    5、pthread_mutex_trylock()函数避免死锁

    以下是一个使用 C 语言实现互斥锁通过 pthread_mutex_trylock() 来避免死锁的发生简单示例代码:

    #include 
    #include
    #include

    pthread_mutex_t lock1, lock2;

    void *thread1(void *arg) {
    while (1) {
    // 循环尝试获取锁 1
    if (pthread_mutex_trylock(&lock1) == 0) {
    printf("Thread 1: Acquired lock 1\n");
    break;
    }
    printf("Thread 1: Failed to acquire lock 1, retrying...\n");
    sleep(1); // 等待一段时间再重试
    }

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

    while (1) {
    // 循环尝试获取锁 2
    if (pthread_mutex_trylock(&lock2) == 0) {
    printf("Thread 1: Acquired lock 2\n");
    break;
    }
    printf("Thread 1: Failed to acquire lock 2, retrying...\n");
    pthread_mutex_unlock(&lock1); // 释放锁 1
    sleep(1); // 等待一段时间再重试
    }

    // 释放锁 2 和锁 1
    pthread_mutex_unlock(&lock2);
    pthread_mutex_unlock(&lock1);

    return NULL;
    }

    void *thread2(void *arg) {
    while (1) {
    // 循环尝试获取锁 2
    if (pthread_mutex_trylock(&lock2) == 0) {
    printf("Thread 2: Acquired lock 2\n");
    break;
    }
    printf("Thread 2: Failed to acquire lock 2, retrying...\n");
    sleep(1); // 等待一段时间再重试
    }

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

    while (1) {
    // 循环尝试获取锁 1
    if (pthread_mutex_trylock(&lock1) == 0) {
    printf("Thread 2: Acquired lock 1\n");
    break;
    }
    printf("Thread 2: Failed to acquire lock 1, retrying...\n");
    pthread_mutex_unlock(&lock2); // 释放锁 2
    sleep(1); // 等待一段时间再重试
    }

    // 释放锁 1 和锁 2
    pthread_mutex_unlock(&lock1);
    pthread_mutex_unlock(&lock2);

    return NULL;
    }

    int main() {
    pthread_t t1, t2;

    // 初始化锁
    pthread_mutex_init(&lock1, NULL);
    pthread_mutex_init(&lock2, NULL);

    // 创建线程
    pthread_create(&t1, NULL, thread1, NULL);
    pthread_create(&t2, NULL, thread2, NULL);

    // 等待线程结束
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    // 销毁锁
    pthread_mutex_destroy(&lock1);
    pthread_mutex_destroy(&lock2);

    return 0;
    }

    在上述示例中,我们进行了如下操作:

    1. 首先,在程序中定义了两个互斥锁 lock1 和 lock2,用于控制线程对资源的访问。

    2. thread1 函数:

    • 它通过一个无休止的 while 循环来不断尝试获取 lock1 锁。
    • 一旦成功获取,会输出相应的成功获取信息,并立即跳出当前的循环。
    • 倘若获取失败,会输出失败提示,并等待 1 秒钟,然后继续下一轮的获取尝试。
    • 在成功获取 lock1 之后,经过 1 秒钟的模拟操作,再次进入另一个 while 循环,尝试获取 lock2 。
    • 若获取 lock2 失败,会输出失败信息,同时释放之前已获取到的 lock1 ,接着等待 1 秒钟,再次进行获取尝试。
    • 当成功获取到 lock2 后,按顺序释放 lock2 和 lock1 ,完成线程的操作。
  • thread2 函数:

    • 其逻辑与 thread1 相似。首先通过无限的 while 循环尝试获取 lock2 。
    • 成功获取时输出信息并跳出循环,失败则输出提示、等待 1 秒后重新尝试。
    • 在成功获取 lock2 并经过 1 秒模拟操作后,进入新的循环尝试获取 lock1 。
    • 若获取 lock1 失败,同样输出失败提示,释放 lock2 ,等待 1 秒后重试。
    • 最终成功获取并按序释放 lock1 和 lock2 。
  • main 函数:

    • 声明了两个线程变量 t1 和 t2 。
    • 对两个互斥锁进行初始化操作。
    • 分别创建两个线程,让它们执行 thread1 和 thread2 函数。
    • 使用 pthread_join 函数等待这两个线程完成执行。
    • 最后,销毁两个互斥锁,释放相关资源。

    这种通过循环尝试获取锁的方式,能够一定程度上应对获取锁失败的情况,避免线程因单次获取失败而直接终止或出现错误,增强了程序的稳定性和适应性。但需要注意,这种频繁的重试可能会带来一定的系统开销,所以在实际运用中,要根据具体场景合理设定等待时间和重试策略,以达到性能和可靠性的平衡。

    程序运行结果如下:

    [root@localhost multi_pthread_file]# ./dead_lock_avoid
    Thread 1: Acquired lock 1
    Thread 2: Acquired lock 2
    Thread 1: Failed to acquire lock 2, retrying...
    Thread 2: Failed to acquire lock 1, retrying...
    Thread 1: Acquired lock 2
    Thread 2: Acquired lock 1

    通过程序运行结果可知,多个线程使用 pthread_mutex_trylock 加锁,可以避免死锁。

    七、总结

    互斥锁(Mutex,Mutual Exclusion Lock)是一种用于多线程环境中的同步机制,具有以下关键特点和用途:

    1. 资源保护:确保在同一时刻只有一个线程能够访问被保护的共享资源,防止数据竞争和不一致性。
    2. 原子操作:保证对共享资源的操作是原子性的,即要么完全执行,要么完全不执行,避免中间状态被其他线程观察到。
    3. 同步协调:使多个线程能够按照预定的顺序和条件进行协作,避免混乱和错误。
    4. 互斥性:当一个线程获取互斥锁后,其他线程在该锁被释放之前无法获取,从而实现对关键代码段或资源的独占访问。

    在使用互斥锁时,需要注意正确的加锁和解锁顺序,避免死锁等问题。同时,过度使用互斥锁可能导致性能下降,因为线程可能会因等待锁而阻塞。因此,在设计多线程程序时,需要仔细考虑互斥锁的使用位置和时机,以达到最佳的性能和正确性平衡。


    Linux二进制 Linux编程、内核模块、网络原创文章分享,欢迎关注"Linux二进制"微信公众号
    评论
    • 构建巨量的驾驶场景时,测试ADAS和AD系统面临着巨大挑战,如传统的实验设计(Design of Experiments, DoE)方法难以有效覆盖识别驾驶边缘场景案例,但这些边缘案例恰恰是进一步提升自动驾驶系统性能的关键。一、传统解决方案:静态DoE标准的DoE方案旨在系统性地探索场景的参数空间,从而确保能够实现完全的测试覆盖范围。但在边缘案例,比如暴露在潜在安全风险的场景或是ADAS系统性能极限场景时,DoE方案通常会失效,让我们看一些常见的DoE方案:1、网格搜索法(Grid)实现原理:将
      康谋 2025-02-27 10:00 252浏览
    • 一、VSM的基本原理震动样品磁强计(Vibrating Sample Magnetometer,简称VSM)是一种灵敏且高效的磁性测量仪器。其基本工作原理是利用震动样品在探测线圈中引起的变化磁场来产生感应电压,这个感应电压与样品的磁矩成正比。因此,通过测量这个感应电压,我们就能够精确地确定样品的磁矩。在VSM中,被测量的样品通常被固定在一个震动头上,并以一定的频率和振幅震动。这种震动在探测线圈中引起了变化的磁通量,从而产生了一个交流电信号。这个信号的幅度和样品的磁矩有着直接的关系。因此,通过仔细
      锦正茂科技 2025-02-28 13:30 100浏览
    •           近日受某专业机构邀请,参加了官方举办的《广东省科技创新条例》宣讲会。在与会之前,作为一名技术工作者一直认为技术的法例都是保密和侵权方面的,而潜意识中感觉法律有束缚创新工作的进行可能。通过一个上午学习新法,对广东省的科技创新有了新的认识。广东是改革的前沿阵地,是科技创新的沃土,企业是创新的主要个体。《广东省科技创新条例》是广东省为促进科技创新、推动高质量发展而制定的地方性法规,主要内容包括: 总则:明确立法目
      广州铁金刚 2025-02-28 10:14 103浏览
    • Matter 协议,原名 CHIP(Connected Home over IP),是由苹果、谷歌、亚马逊和三星等科技巨头联合ZigBee联盟(现连接标准联盟CSA)共同推出的一套基于IP协议的智能家居连接标准,旨在打破智能家居设备之间的 “语言障碍”,实现真正的互联互通。然而,目标与现实之间总有落差,前期阶段的Matter 协议由于设备支持类型有限、设备生态协同滞后以及设备通信协议割裂等原因,并未能彻底消除智能家居中的“设备孤岛”现象,但随着2025年的到来,这些现象都将得到完美的解决。近期,
      华普微HOPERF 2025-02-27 10:32 214浏览
    •         近日,广电计量在聚焦离子束(FIB)领域编写的专业著作《聚焦离子束:失效分析》正式出版,填补了国内聚焦离子束领域实践性专业书籍的空白,为该领域的技术发展与知识传播提供了重要助力。         随着芯片技术不断发展,芯片的集成度越来越高,结构也日益复杂。这使得传统的失效分析方法面临巨大挑战。FIB技术的出现,为芯片失效分析带来了新的解决方案。它能够在纳米尺度上对芯片进行精确加工和分析。当芯
      广电计量 2025-02-28 09:15 116浏览
    • 振动样品磁强计是一种用于测量材料磁性的精密仪器,广泛应用于科研、工业检测等领域。然而,其测量准确度会受到多种因素的影响,下面我们将逐一分析这些因素。一、温度因素温度是影响振动样品磁强计测量准确度的重要因素之一。随着温度的变化,材料的磁性也会发生变化,从而影响测量结果的准确性。因此,在进行磁性测量时,应确保恒温环境,以减少温度波动对测量结果的影响。二、样品制备样品的制备过程同样会影响振动样品磁强计的测量准确度。样品的形状、尺寸和表面处理等因素都会对测量结果产生影响。为了确保测量准确度,应严格按照规
      锦正茂科技 2025-02-28 14:05 134浏览
    • 在物联网领域中,无线射频技术作为设备间通信的核心手段,已深度渗透工业自动化、智慧城市及智能家居等多元场景。然而,随着物联网设备接入规模的不断扩大,如何降低运维成本,提升通信数据的传输速度和响应时间,实现更广泛、更稳定的覆盖已成为当前亟待解决的系统性难题。SoC无线收发模块-RFM25A12在此背景下,华普微创新推出了一款高性能、远距离与高性价比的Sub-GHz无线SoC收发模块RFM25A12,旨在提升射频性能以满足行业中日益增长与复杂的设备互联需求。值得一提的是,RFM25A12还支持Wi-S
      华普微HOPERF 2025-02-28 09:06 143浏览
    • 1,微软下载免费Visual Studio Code2,安装C/C++插件,如果无法直接点击下载, 可以选择手动install from VSIX:ms-vscode.cpptools-1.23.6@win32-x64.vsix3,安装C/C++编译器MniGW (MinGW在 Windows 环境下提供类似于 Unix/Linux 环境下的开发工具,使开发者能够轻松地在 Windows 上编写和编译 C、C++ 等程序.)4,C/C++插件扩展设置中添加Include Path 5,
      黎查 2025-02-28 14:39 140浏览
    • 请移步 gitee 仓库 https://gitee.com/Newcapec_cn/LiteOS-M_V5.0.2-Release_STM32F103_CubeMX/blob/main/Docs/%E5%9F%BA%E4%BA%8ESTM32F103RCT6%E7%A7%BB%E6%A4%8DLiteOS-M-V5.0.2-Release.md基于STM32F103RCT6移植LiteOS-M-V5.0.2-Release下载源码kernel_liteos_m: OpenHarmon
      逮到一只程序猿 2025-02-27 08:56 195浏览
    • 在2024年的科技征程中,具身智能的发展已成为全球关注的焦点。从实验室到现实应用,这一领域正以前所未有的速度推进,改写着人类与机器的互动边界。这一年,我们见证了具身智能技术的突破与变革,它不仅落地各行各业,带来新的机遇,更在深刻影响着我们的生活方式和思维方式。随着相关技术的飞速发展,具身智能不再仅仅是一个技术概念,更像是一把神奇的钥匙。身后的众多行业,无论愿意与否,都像是被卷入一场伟大变革浪潮中的船只,注定要被这股汹涌的力量重塑航向。01为什么是具身智能?为什么在中国?最近,中国具身智能行业的进
      艾迈斯欧司朗 2025-02-28 15:45 221浏览
    • RGB灯光无法同步?细致的动态光效设定反而成为产品客诉来源!随着科技的进步和消费者需求变化,电脑接口设备单一功能性已无法满足市场需求,因此在产品上增加「动态光效」的形式便应运而生,藉此吸引消费者目光。这种RGB灯光效果,不仅能增强电脑周边产品的视觉吸引力,还能为用户提供个性化的体验,展现独特自我风格。如今,笔记本电脑、键盘、鼠标、鼠标垫、耳机、显示器等多种电脑接口设备多数已配备动态光效。这些设备的灯光效果会随着音乐节奏、游戏情节或使用者的设置而变化。想象一个画面,当一名游戏玩家,按下电源开关,整
      百佳泰测试实验室 2025-02-27 14:15 137浏览
    • 美国加州CEC能效跟DOE能效有什么区别?CEC/DOE是什么关系?美国加州CEC能效跟DOE能效有什么区别?CEC/DOE是什么关系?‌美国加州CEC能效认证与美国DOE能效认证在多个方面存在显著差异‌。认证范围和适用地区‌CEC能效认证‌:仅适用于在加利福尼亚州销售的电器产品。CEC认证的范围包括制冷设备、房间空调、中央空调、便携式空调、加热器、热水器、游泳池加热器、卫浴配件、光源、应急灯具、交通信号模块、灯具、洗碗机、洗衣机、干衣机、烹饪器具、电机和压缩机、变压器、外置电源、消费类电子设备
      张工nx808593 2025-02-27 18:04 120浏览
    • 应用趋势与客户需求,AI PC的未来展望随着人工智能(AI)技术的日益成熟,AI PC(人工智能个人电脑)逐渐成为消费者和企业工作中的重要工具。这类产品集成了最新的AI处理器,如NPU、CPU和GPU,并具备许多智能化功能,为用户带来更高效且直观的操作体验。AI PC的目标是提升工作和日常生活的效率,通过深度学习与自然语言处理等技术,实现更流畅的多任务处理、实时翻译、语音助手、图像生成等功能,满足现代用户对生产力和娱乐的双重需求。随着各行各业对数字转型需求的增长,AI PC也开始在各个领域中显示
      百佳泰测试实验室 2025-02-27 14:08 255浏览
    • 更多生命体征指标风靡的背后都只有一个原因:更多人将健康排在人生第一顺位!“AGEs,也就是晚期糖基化终末产物,英文名Advanced Glycation End-products,是存在于我们体内的一种代谢产物” 艾迈斯欧司朗亚太区健康监测高级市场经理王亚琴说道,“相信业内的朋友都会有关注,最近该指标的热度很高,它可以用来评估人的生活方式是否健康。”据悉,AGEs是可穿戴健康监测领域的一个“萌新”指标,近来备受关注。如果站在学术角度来理解它,那么AGEs是在非酶促条件下,蛋白质、氨基酸
      艾迈斯欧司朗 2025-02-27 14:50 400浏览
    我要评论
    0
    点击右上角,分享到朋友圈 我知道啦
    请使用浏览器分享功能 我知道啦