pthread_mutex_t死锁

来源:互联网 发布:魔兽世界幻化软件 编辑:程序博客网 时间:2024/05/22 00:27

互斥量,也叫互斥锁。通常造成死锁的有两种方式:


1.线程A试图对用一个互斥量mutexA加锁两次,那么它自身就会陷入死锁状态,

用伪代码表示就是:


pthreadA:

[cpp] view plain copy
print?
  1. pthread_mutex_lock(&mutexA)  
  2. pthread_mutex_lock(&mutexA) /* 这里死锁 */  


2.程序中使用多个互斥量时,如果允许一个线程一直占有第一个互斥量,并且试图锁住第二个互斥量时处于阻塞状态,但是拥有第二个互斥量的线程也在试图锁住第一个互斥量,这时就发生死锁。因为两个线程都在互相请求另一个线程拥有的资源,所以这两个线程都无法向前运行,于是产生死锁。

用伪代码表示就是:


pthreadA:

[cpp] view plain copy
print?
  1. pthread_mutex_lock(&mutexA)  
  2. pthread_mutex_lock(&mutexB) /* 这里死锁 */  

pthreadB:

[cpp] view plain copy
print?
  1. pthread_mutex_lock(&mutexB)  
  2. pthread_mutex_lock(&mutexA) /* 这里死锁 */  


所以,使用互斥锁的时候一定要注意。

产生死锁的四个必要条件

(1) 互斥条件:一个资源每次只能被一个进程(线程)使用。
(2) 请求与保持条件:一个进程(线程)因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件 : 此进程(线程)已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件 : 多个进程(线程)之间形成一种头尾相接的循环等待资源关系。

图 1. 交叉持锁的死锁示意图:
图 1. 交叉持锁的死锁示意图:

注释:在执行 func2 和 func4 之后,子线程 1 获得了锁 A,正试图获得锁 B,但是子线程 2 此时获得了锁 B,正试图获得锁 A,所以子线程 1 和子线程 2 将没有办法得到锁 A 和锁 B,因为它们各自被对方占有,永远不会释放,所以发生了死锁的现象。

使用 pstack 和 gdb 工具对死锁程序进行分析

pstack 在 Linux 平台上的简单介绍

pstack 是 Linux(比如 Red Hat Linux 系统、Ubuntu Linux 系统等)下一个很有用的工具,它的功能是打印输出此进程的堆栈信息。可以输出所有线程的调用关系栈。

gdb 在 Linux 平台上的简单介绍

GDB 是 GNU 开源组织发布的一个强大的 UNIX 下的程序调试工具。Linux 系统中包含了 GNU 调试程序 gdb,它是一个用来调试 C 和 C++ 程序的调试器。可以使程序开发者在程序运行时观察程序的内部结构和内存的使用情况 .

gdb 所提供的一些主要功能如下所示:

1 运行程序,设置能影响程序运行的参数和环境 ;

2 控制程序在指定的条件下停止运行;

3 当程序停止时,可以检查程序的状态;

4 当程序 crash 时,可以检查 core 文件;

5 可以修改程序的错误,并重新运行程序;

6 可以动态监视程序中变量的值;

7 可以单步执行代码,观察程序的运行状态。

gdb 程序调试的对象是可执行文件或者进程,而不是程序的源代码文件。然而,并不是所有的可执行文件都可以用 gdb 调试。如果要让产生的可执行文件可以用来调试,需在执行 g++(gcc)指令编译程序时,加上 -g 参数,指定程序在编译时包含调试信息。调试信息包含程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号。gdb 利用这些信息使源代码和机器码相关联。gdb 的基本命令较多,不做详细介绍,大家如果需要进一步了解,请参见 gdb 手册。

清单 1. 测试程序
 #include <unistd.h>  #include <pthread.h>  #include <string.h>  pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;  pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;  pthread_mutex_t mutex3 = PTHREAD_MUTEX_INITIALIZER;  pthread_mutex_t mutex4 = PTHREAD_MUTEX_INITIALIZER;  static int sequence1 = 0;  static int sequence2 = 0;  int func1()  {     pthread_mutex_lock(&mutex1);     ++sequence1;     sleep(1);     pthread_mutex_lock(&mutex2);     ++sequence2;     pthread_mutex_unlock(&mutex2);     pthread_mutex_unlock(&mutex1);     return sequence1;  }  int func2()  {     pthread_mutex_lock(&mutex2);     ++sequence2;     sleep(1);     pthread_mutex_lock(&mutex1);     ++sequence1;     pthread_mutex_unlock(&mutex1);     pthread_mutex_unlock(&mutex2);     return sequence2;  }  void* thread1(void* arg)  {     while (1)     {         int iRetValue = func1();         if (iRetValue == 100000)         {             pthread_exit(NULL);         }     }  }  void* thread2(void* arg)  {     while (1)     {         int iRetValue = func2();         if (iRetValue == 100000)         {             pthread_exit(NULL);         }     }  }  void* thread3(void* arg)  {     while (1)     {         sleep(1);         char szBuf[128];         memset(szBuf, 0, sizeof(szBuf));         strcpy(szBuf, "thread3");     }  }  void* thread4(void* arg)  {     while (1)     {         sleep(1);         char szBuf[128];         memset(szBuf, 0, sizeof(szBuf));         strcpy(szBuf, "thread3");     }  }  int main()  {     pthread_t tid[4];     if (pthread_create(&tid[0], NULL, &thread1, NULL) != 0)     {         _exit(1);     }     if (pthread_create(&tid[1], NULL, &thread2, NULL) != 0)     {         _exit(1);     }     if (pthread_create(&tid[2], NULL, &thread3, NULL) != 0)     {         _exit(1);     }     if (pthread_create(&tid[3], NULL, &thread4, NULL) != 0)     {         _exit(1);     }     sleep(5);     //pthread_cancel(tid[0]);     pthread_join(tid[0], NULL);     pthread_join(tid[1], NULL);     pthread_join(tid[2], NULL);     pthread_join(tid[3], NULL);     pthread_mutex_destroy(&mutex1);     pthread_mutex_destroy(&mutex2);     pthread_mutex_destroy(&mutex3);     pthread_mutex_destroy(&mutex4);     return 0;  }
清单 2. 编译测试程序
 [dyu@xilinuxbldsrv purify]$ g++ -g lock.cpp -o lock -lpthread
清单 3. 查找测试程序的进程号
 [dyu@xilinuxbldsrv purify]$ ps -ef|grep lock  dyu       6721  5751  0 15:21 pts/3    00:00:00 ./lock
清单 4. 对死锁进程第一次执行 pstack(pstack –进程号)的输出结果
 [dyu@xilinuxbldsrv purify]$ pstack 6721  Thread 5 (Thread 0x41e37940 (LWP 6722)):  #0  0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0  #1  0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0  #2  0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0  #3  0x0000000000400a9b in func1() ()  #4  0x0000000000400ad7 in thread1(void*) ()  #5  0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0  #6  0x0000003d19cd40cd in clone () from /lib64/libc.so.6  Thread 4 (Thread 0x42838940 (LWP 6723)):  #0  0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0  #1  0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0  #2  0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0  #3  0x0000000000400a17 in func2() ()  #4  0x0000000000400a53 in thread2(void*) ()  #5  0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0  #6  0x0000003d19cd40cd in clone () from /lib64/libc.so.6  Thread 3 (Thread 0x43239940 (LWP 6724)):  #0  0x0000003d19c9a541 in nanosleep () from /lib64/libc.so.6  #1  0x0000003d19c9a364 in sleep () from /lib64/libc.so.6  #2  0x00000000004009bc in thread3(void*) ()  #3  0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0  #4  0x0000003d19cd40cd in clone () from /lib64/libc.so.6  Thread 2 (Thread 0x43c3a940 (LWP 6725)):  #0  0x0000003d19c9a541 in nanosleep () from /lib64/libc.so.6  #1  0x0000003d19c9a364 in sleep () from /lib64/libc.so.6  #2  0x0000000000400976 in thread4(void*) ()  #3  0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0  #4  0x0000003d19cd40cd in clone () from /lib64/libc.so.6  Thread 1 (Thread 0x2b984ecabd90 (LWP 6721)):  #0  0x0000003d1a807b35 in pthread_join () from /lib64/libpthread.so.0  #1  0x0000000000400900 in main ()
清单 5. 对死锁进程第二次执行 pstack(pstack –进程号)的输出结果
 [dyu@xilinuxbldsrv purify]$ pstack 6721  Thread 5 (Thread 0x40bd6940 (LWP 6722)):  #0  0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0  #1  0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0  #2  0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0  #3  0x0000000000400a87 in func1() ()  #4  0x0000000000400ac3 in thread1(void*) ()  #5  0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0  #6  0x0000003d19cd40cd in clone () from /lib64/libc.so.6  Thread 4 (Thread 0x415d7940 (LWP 6723)):  #0  0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0  #1  0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0  #2  0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0  #3  0x0000000000400a03 in func2() ()  #4  0x0000000000400a3f in thread2(void*) ()  #5  0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0  #6  0x0000003d19cd40cd in clone () from /lib64/libc.so.6  Thread 3 (Thread 0x41fd8940 (LWP 6724)):  #0  0x0000003d19c7aec2 in memset () from /lib64/libc.so.6  #1  0x00000000004009be in thread3(void*) ()  #2  0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0  #3  0x0000003d19cd40cd in clone () from /lib64/libc.so.6  Thread 2 (Thread 0x429d9940 (LWP 6725)):  #0  0x0000003d19c7ae0d in memset () from /lib64/libc.so.6  #1  0x0000000000400982 in thread4(void*) ()  #2  0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0  #3  0x0000003d19cd40cd in clone () from /lib64/libc.so.6  Thread 1 (Thread 0x2af906fd9d90 (LWP 6721)):  #0  0x0000003d1a807b35 in pthread_join () from /lib64/libpthread.so.0  #1  0x0000000000400900 in main ()

连续多次查看这个进程的函数调用关系堆栈进行分析:当进程吊死时,多次使用 pstack 查看进程的函数调用堆栈,死锁线程将一直处于等锁的状态,对比多次的函数调用堆栈输出结果,确定哪两个线程(或者几个线程)一直没有变化且一直处于等锁的状态(可能存在两个线程 一直没有变化)。

输出分析:

根据上面的输出对比可以发现,线程 1 和线程 2 由第一次 pstack 输出的处在 sleep 函数变化为第二次 pstack 输出的处在 memset 函数。但是线程 4 和线程 5 一直处在等锁状态(pthread_mutex_lock),在连续两次的 pstack 信息输出中没有变化,所以我们可以推测线程 4 和线程 5 发生了死锁。

Gdb into thread输出:

清单 6. 然后通过 gdb attach 到死锁进程
   (gdb) info thread   5 Thread 0x41e37940 (LWP 6722)  0x0000003d1a80d4c4 in __lll_lock_wait ()   from /lib64/libpthread.so.0   4 Thread 0x42838940 (LWP 6723)  0x0000003d1a80d4c4 in __lll_lock_wait ()   from /lib64/libpthread.so.0   3 Thread 0x43239940 (LWP 6724)  0x0000003d19c9a541 in nanosleep ()  from /lib64/libc.so.6   2 Thread 0x43c3a940 (LWP 6725)  0x0000003d19c9a541 in nanosleep ()  from /lib64/libc.so.6  * 1 Thread 0x2b984ecabd90 (LWP 6721)  0x0000003d1a807b35 in pthread_join ()  from /lib64/libpthread.so.0
清单 7. 切换到线程 5 的输出
 (gdb) thread 5  [Switching to thread 5 (Thread 0x41e37940 (LWP 6722))]#0  0x0000003d1a80d4c4 in  __lll_lock_wait () from /lib64/libpthread.so.0  (gdb) where  #0  0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0  #1  0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0  #2  0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0  #3  0x0000000000400a9b in func1 () at lock.cpp:18  #4  0x0000000000400ad7 in thread1 (arg=0x0) at lock.cpp:43  #5  0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0  #6  0x0000003d19cd40cd in clone () from /lib64/libc.so.6
清单 8. 线程 4 和线程 5 的输出
 (gdb) f 3  #3  0x0000000000400a9b in func1 () at lock.cpp:18  18          pthread_mutex_lock(&mutex2);  (gdb) thread 4  [Switching to thread 4 (Thread 0x42838940 (LWP 6723))]#0  0x0000003d1a80d4c4 in  __lll_lock_wait () from /lib64/libpthread.so.0  (gdb) f 3  #3  0x0000000000400a17 in func2 () at lock.cpp:31  31          pthread_mutex_lock(&mutex1);  (gdb) p mutex1  $1 = {__data = {__lock = 2, __count = 0, __owner = 6722, __nusers = 1, __kind = 0,  __spins = 0, __list = {__prev = 0x0, __next = 0x0}},   __size = "\002\000\000\000\000\000\000\000B\032\000\000\001", '\000' <repeats 26 times>, __align = 2}  (gdb) p mutex3  $2 = {__data = {__lock = 0, __count = 0, __owner = 0, __nusers = 0,  __kind = 0, __spins = 0, __list = {__prev = 0x0, __next = 0x0}},  __size = '\000' <repeats 39 times>, __align = 0}  (gdb) p mutex2  $3 = {__data = {__lock = 2, __count = 0, __owner = 6723, __nusers = 1,  __kind = 0, __spins = 0, __list = {__prev = 0x0, __next = 0x0}},   __size = "\002\000\000\000\000\000\000\000C\032\000\000\001", '\000' <repeats 26 times>, __align = 2}  (gdb)

从上面可以发现,线程 4 正试图获得锁 mutex1,但是锁 mutex1 已经被 LWP 为 6722 的线程得到(__owner = 6722),线程 5 正试图获得锁 mutex2,但是锁 mutex2 已经被 LWP 为 6723 的 得到(__owner = 6723),从 pstack 的输出可以发现,LWP 6722 与线程 5 是对应的,LWP 6723 与线程 4 是对应的。所以我们可以得出, 线程 4 和线程 5 发生了交叉持锁的死锁现象。查看线程的源代码发现,线程 4 和线程 5 同时使用 mutex1 和 mutex2,且申请顺序不合理。

小结

本文简单介绍了一种在 Linux 平台下分析死锁问题的方法,对一些死锁问题的分析有一定作用。希望对大家有帮助。理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态的情况下占用资源 , 在系统运行过程中,对进程发出的每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,若分配后系统可能发生死锁,则不予分配,否则予以分配。因此,对资源的分配要给予合理的规划,使用有序资源分配法和银行家算法等是避免死锁的有效方法。


使用上pthread_cond_t遇到的死锁问题 

最近在一个项目中使用pthread_cond_t的时遇到一个死锁的问题,特记录分享一下。这个问题的使用场景很简单,客户端程序起两个线程,一个线程发送数据给服务器,另一个线程接收服务器的返回。发送线程向服务器发送一个数据报,然后等待服务器返回(用pthread_cond_t等待),然后继续发送下一个数据包……,如此循环下去。发送代码抽取出来的逻辑像下面这样:


static void* thread_fun_send(void* nouse)

{

    int send_index = 0;

    while(1)

    {

        //模拟发送数据的socket系统调用

        printf("send %d\n",send_index++);

 

        //通知接收线程可以接收数据

        pthread_mutex_lock(&mutex_send);

        pthread_cond_signal(&cond_send);

        pthread_mutex_unlock(&mutex_send);

 

        //等待收到数据的pthread_cond_t

        pthread_mutex_lock(&mutex_recv);

        pthread_cond_wait(&cond_recv, &mutex_recv);

        pthread_mutex_unlock(&mutex_recv);

    }  

}


接收线程等待服务器的返回,收到服务器返回后给发送线程发信号(用pthread_cond_t发信号),然后继续等待接收服务器下一个返回……,如此循环下去。接收代码抽取出来的逻辑像下面这样:

static void* thread_fun_recv(void* nouse)

{

    int recv_index = 0;

    while(1)

    {  

        //等待收到数据的pthread_cond_t,模拟阻塞的socket

        pthread_mutex_lock(&mutex_send);

        pthread_cond_wait(&cond_send, &mutex_send);

        pthread_mutex_unlock(&mutex_send);

 

        //模拟接收数据的socket系统调用

        printf("recv %d\n",recv_index++);

 

        //通知发送线程数据已经收到

        pthread_mutex_lock(&mutex_recv);

        pthread_cond_signal(&cond_recv);

        pthread_mutex_unlock(&mutex_recv);

    }  

}

就这么一个简单的应用场景,看起来好像是很合乎逻辑的,但在实际的运行中会出现死锁的问题。经过综合分析后发现其实是在理解pthread_cond_t上不准确了,一开始会认为它和windows中的Event是一样的,只是调用的API函数名不同而已,其实它们是不同的。且不说自动重置这个功能,在信号的处理上它们也是完全不同的。在Windows中,当Event被置为有信号时,内核会检查有没有在等待这个信号的线程,如果有则唤醒它,如果没有信号会保存起来,下一个等待此信号的线程进来时会直接获得信号而继续运行;但在Linux中, 如果pthread_cond_t被置为有信号时,内核也是会检查有没有在等待这个信号的线程,如果有则唤醒它,但如果没有等待的线程内核则不会保存这个信号(好像这个信号被丢掉了一样),下一个等待信号的线程进来时是不会获得信号而只会进入睡眠的,只有在睡眠后下一次pthread_cond_t被置为有信号时,这个线程才会被唤醒。


搞清楚了pthread_cond_t和Event的逻辑之后,对于上面这个客户端的业务逻辑,用最小的改动方式就是把使用pthread_cond_t改为sem_t问题就解决了。

附:测试代码,用下面命令即可编译
gcc -o test_cond test_cond.c -pthread

点击(此处)折叠或打开

  1. // File Name: test_cond.c
  2. // Purpose:For Linux pthread_cond_t test
  3. #include <pthread.h>
  4. #include <unistd.h>
  5. #include <stdio.h>
  6. #include <stdlib.h>

  7. static pthread_cond_t cond_send;
  8. static pthread_mutex_t mutex_send;

  9. static pthread_cond_t cond_recv;
  10. static pthread_mutex_t mutex_recv;

  11. static pthread_t thread_send;
  12. static pthread_t thread_recv;

  13. static void* thread_fun_send(void* nouse)
  14. {
  15.     int send_index = 0;

  16.     //wait a momentfor recv thread starting up
  17.     sleep(1);

  18.     while(1)
  19.     {
  20.         printf("send %d\n",send_index++);

  21.         pthread_mutex_lock(&mutex_send);
  22.         pthread_cond_signal(&cond_send);
  23.         pthread_mutex_unlock(&mutex_send);

  24.         pthread_mutex_lock(&mutex_recv);
  25.         pthread_cond_wait(&cond_recv,&mutex_recv);
  26.         pthread_mutex_unlock(&mutex_recv);

  27.     }
  28. }

  29. static void* thread_fun_recv(void* nouse)
  30. {
  31.     int recv_index = 0;
  32.     while(1)
  33.     {
  34.         pthread_mutex_lock(&mutex_send);
  35.         pthread_cond_wait(&cond_send,&mutex_send);
  36.         pthread_mutex_unlock(&mutex_send);

  37.         printf("recv %d\n",recv_index++);

  38.         pthread_mutex_lock(&mutex_recv);
  39.         pthread_cond_signal(&cond_recv);
  40.         pthread_mutex_unlock(&mutex_recv);
  41.     }
  42. }

  43. void init()
  44. {
  45.     pthread_mutex_init(&mutex_send, 0);
  46.     pthread_cond_init(&cond_send, 0);

  47.     pthread_mutex_init(&mutex_recv, 0);
  48.     pthread_cond_init(&cond_recv, 0);

  49.     pthread_create(&thread_send,NULL ,thread_fun_send,NULL);
  50.     pthread_create(&thread_recv,NULL ,thread_fun_recv,NULL);
  51. }

  52. void clean()
  53. {
  54.     pthread_cond_destroy(&cond_send);
  55.     pthread_mutex_destroy(&mutex_send);

  56.     pthread_cond_destroy(&cond_recv);
  57.     pthread_mutex_destroy(&mutex_recv);
  58. }


  59. void main()
  60. {
  61.     printf("Test pthread_cond_t applet\n");
  62.     
  63.     init();

  64.     while(1)
  65.     {
  66.         sleep(1);
  67.     }

  68.     clean();
  69.     return;
  70. }

原创粉丝点击