互斥锁的死锁

简介: 互斥锁的死锁

互斥锁在默认属性的情况下使用,一般需要关注死锁的情况。所谓死锁,即互斥锁无法解除同时也无法加持,导致程序可能会无限阻塞的情况。有时,一个线程可能会同时访问多个不同的共享资源,而每个共享资源都需要有不同互斥锁管理。那么在不经意间程序编写极容易造成死锁的情况。造成死锁的原因有很多:


  1. 在互斥锁默认属性的情况下,在同一个线程中不允许对同一互斥锁连续进行加锁操作。因为之前锁处于未解除状态,如果再次对同一个互斥锁进行加锁,那么必然会导致程序无限阻塞等待。
  2. 多个线程对多个互斥锁交叉使用,每一个线程都试图对其他线程所持有的互斥锁进行加锁。如下图所示情况,线程分别持有了对方需要的锁资源,并相互影响,可能会导致程序无限阻塞,就会造成死锁。


微信截图_20221209154415.png


同时,还需要注意的是在一个线程中操作多个互斥锁时,加锁与解锁的顺序一定是相反的,否则也会导致错误。如上图所示,如果线程先加锁 1 ,后加锁 2 ,之后一定要先解锁 2 ,再解锁 1


  1. 一个持有互斥锁的线程被其他线程取消,其他线程将无法获得该锁,则会造成死锁。具体如下所示:
#include <pthread.h>
#include <stdio.h>
#define errlog(errmsg)                                                \
    do {                                                              \
        perror(errmsg);                                               \
        printf("--%s--%s--%d--\n", __FILE__, __FUNCTION__, __LINE__); \
        return -1;                                                    \
    } while (0)
int value1 = 0;
int value2 = 0;
int count = 0;
pthread_mutex_t lock;
void *thread1_handler(void *arg) {
    while (1) {
    pthread_mutex_lock(&lock);
        value1 = count;
        value2 = count;
        count++;
        sleep(3);
    pthread_mutex_unlock(&lock);
    }
    pthread_exit(0);
}
void *thread2_handler(void *arg) {
    while (1) {
        sleep(1);
    pthread_mutex_lock(&lock);
        if (value1 != value2) {
            sleep(1);
            printf("value1 = %d value2 = %d\n", value1, value2);
        }
    pthread_mutex_unlock(&lock);
    }
    pthread_exit(0);
}
int main(int argc, const char *argv[]) {
    pthread_t thread1, thread2;
  if(pthread_mutex_init(&lock,NULL)!=0){
    errlog("pthread_mutex_init error");
  }
    if (pthread_create(&thread1, NULL, thread1_handler, NULL) != 0) {
        errlog("pthread_create1 error");
    }
    if (pthread_create(&thread2, NULL, thread2_handler, NULL) != 0) {
        errlog("pthread_create2 error");
    }
    sleep(2);
    pthread_cancel(thread1);
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
  pthread_mutex_destroy(&lock);
    return 0;
}点击复制复制失败已复制


上述代码对线程进行延迟处理,确保线程 1 先获取互斥锁,并在线程 1 持有互斥锁期间被取消。此时线程 1 使用的互斥锁将无法被获取,造成死锁。


为了规避这类问题,线程可以设置一个或多个清理函数,当线程遭取消时会自动运行这些函数。在线程终止前可执行诸如修改全局变量、解锁等操作。每一个线程都有一个清理函数栈。当线程遭取消时,会沿该栈顶自顶向下依次执行清理函数。当执行完所有的清理函数后,线程终止。 pthread_cleanup_push() 函数和 pthread_cleanup_pop() 函数分别负责向调用线程的清理函数栈添加和移除清理函数。

#include <pthread.h>
void pthread_cleanup_push(void (*routine)(void *), void *arg);
void pthread_cleanup_pop(int execute);点击复制复制失败已复制


执行 pthread_cleanup_push() 函数会将参数 routine 所含的函数地址添加到调用线程的清理函数栈顶arg 作为调用函数的参数,传递给 routine

pthread_cleanup_pop() 函数且参数 execute非零。本笔记讨论的正是线程被取消的情况,将清除函数的功能设置为解除互斥锁,这样其他线程就不会陷入无限期等待状态。具体代码如下所示:

#include <pthread.h>
#include <stdio.h>
#define errlog(errmsg)                                                         \
  do {                                                                         \
    perror(errmsg);                                                            \
    printf("--%s--%s--%d--\n", __FILE__, __FUNCTION__, __LINE__);              \
    return -1;                                                                 \
  } while (0)
int value1 = 0;
int value2 = 0;
int count = 0;
pthread_mutex_t lock;
void cleanup_handler(void *arg) { pthread_mutex_unlock(&lock); }
void *thread1_handler(void *arg) {
  while (1) {
    pthread_mutex_lock(&lock);
    pthread_cleanup_push(cleanup_handler, NULL);
    value1 = count;
    value2 = count;
    count++;
    sleep(3);
    pthread_cleanup_pop(0);
    pthread_mutex_unlock(&lock);
  }
  pthread_exit(0);
}
void *thread2_handler(void *arg) {
  while (1) {
    sleep(1);
    pthread_mutex_lock(&lock);
    if (value1 == value2) {
      sleep(1);
      printf("value1 = %d value2 = %d\n", value1, value2);
    }
    pthread_mutex_unlock(&lock);
  }
  pthread_exit(0);
}
int main(int argc, const char *argv[]) {
  pthread_t thread1, thread2;
  if (pthread_mutex_init(&lock, NULL) != 0) {
    errlog("pthread_mutex_init error");
  }
  if (pthread_create(&thread1, NULL, thread1_handler, NULL) != 0) {
    errlog("pthread_create1 error");
  }
  if (pthread_create(&thread2, NULL, thread2_handler, NULL) != 0) {
    errlog("pthread_create2 error");
  }
  sleep(2);
  pthread_cancel(thread1);
  pthread_join(thread1, NULL);
  pthread_join(thread2, NULL);
  pthread_mutex_destroy(&lock);
  return 0;
}点击复制复制失败已复制


上述代码当线程 1 被取消自动解除互斥锁之后,线程 2 获得该锁,此时 value1value2 的值一定相等,且为 0 。通过运行结果也可验证这一点:

$ gcc main.c && ./a.out 
value1 = 0 value2 = 0
value1 = 0 value2 = 0
value1 = 0 value2 = 0
value1 = 0 value2 = 0
value1 = 0 value2 = 0
value1 = 0 value2 = 0
value1 = 0 value2 = 0
value1 = 0 value2 = 0
value1 = 0 value2 = 0
value1 = 0 value2 = 0
value1 = 0 value2 = 0
目录
相关文章
|
13天前
|
调度
线程操作:锁、条件变量的使用
线程操作:锁、条件变量的使用
14 1
|
6天前
|
Java 调度
阻塞锁和自旋锁的理解
总体来说,自旋锁适用于锁定时间短、锁竞争不频繁的场景,而阻塞锁更适合锁定时间较长或锁竞争较频繁的场景。根据具体的应用需求选择合适的锁类型,可以优化系统性能。
8 0
|
19天前
|
算法
如何避免死锁?
如何避免死锁?
14 0
|
2月前
|
安全
什么是死锁?互斥锁进入死锁怎么解决?
什么是死锁?互斥锁进入死锁怎么解决?
|
2月前
|
调度
互斥锁的初步实现
互斥锁的初步实现
69 0
|
8月前
|
Linux API C++
锁、避免死锁等相关
锁、避免死锁等相关
42 0
|
11月前
|
数据可视化 Java
lock锁和死锁
lock锁和死锁
51 0
|
12月前
|
安全 算法 C++
C++中互斥锁的使用
我们现在有一个需求,我们需要对 g_exceptions 这个 vector 的访问进行同步处理,确保同一时刻只有一个线程能向它插入新的元素。为此我使用了一个 mutex 和一个锁(lock)。mutex 是同步操作的主体,在 C++ 11 的 <mutex> 头文件中,有四种风格的实现: mutex:提供了核心的 lock() unlock() 方法,以及当 mutex 不可用时就会返回的非阻塞方法 try_lock() recursive_mutex:允许同一线程内对同一 mutex 的多重持有 timed_mutex: 与 mutex 类似,但多了 try_lock_for() t
75 0
|
安全 算法 C++
C++11中的互斥锁讲解
C++11中的互斥锁讲解
86 0
多线程死锁的产生以及如何避免死锁
指多个线程因竞争资源而造成的一种僵局(互相等待),具体代码就是同步中嵌套了其他同步,这种现象称为死锁:比如过独木桥问题,其他锁还有可重入锁、可中断锁、公平锁和同步锁
134 0