亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

一個 pthread_cancel 引起的線程死鎖

2019-11-07 23:56:10
字體:
來源:轉載
供稿:網友

說明:本文由【2,3】整理而得。

這篇文章主要從一個 linux 下一個pthread_cancel 函數引起的多線程死鎖小例子出發來說明Linux 系統對 POSIX線程取消點的實現方式,以及如何避免因此產生的線程死鎖。

目 錄:

1. 一個 pthread_cancel 引起的線程死鎖小例子

2. 取消點(Cancellation Point)

3. 取消類型(Cancellation Type)

4. Linux 的取消點實現

5. 對示例函數進入死鎖的解釋

6. 如何避免因此產生的死鎖

7. 結論

8. 參考文獻

1. 一個 pthread_cancel 引起的線程死鎖小例子

下面是一段在Linux 平臺下能引起線程死鎖的小例子。這個實例程序僅僅是使用了條件變量和互斥量進行一個簡單的線程同步,thread0首先啟動,鎖住互斥量 mutex,然后調用pthread_cond_wait,它將線程tid[0] 放在等待條件的線程列表上后,對mutex 解鎖。thread1啟動后等待 10 秒鐘,此時pthread_cond_wait 應該已經將mutex 解鎖,這時 tid[1]線程鎖住 mutex,然后廣播信號喚醒cond 等待條件的所有等待線程,之后解鎖 mutex。當 mutex解鎖后,tid[0] 線程的pthread_cond_wait 函數重新鎖住mutex 并返回,最后 tid[0]再對 mutex 進行解鎖。

示例代碼

復制代碼
#include <pthread.h>#include "stdio.h"#include "stdlib.h"#include "unistd.h"pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t cond = PTHREAD_COND_INITIALIZER;void* thread0(void* arg){    pthread_mutex_lock(&mutex);    PRintf("in thread 0 tag 1/n");    pthread_cond_wait(&cond, &mutex);    printf("in thread 0 tag 2/n");    pthread_mutex_unlock(&mutex);    printf("in thread 0 tag 3/n");    pthread_exit(NULL);}void* thread1(void* arg){    sleep(10);    printf("in thread 1 tag 1/n");    pthread_mutex_lock(&mutex);    printf("in thread 1 tag 2/n");    pthread_cond_broadcast(&cond);    pthread_mutex_unlock(&mutex);    printf("in thread 1 tag 3/n");    pthread_exit(NULL);}int main(){    pthread_t tid[2];    if (pthread_create(&tid[0], NULL, thread0, NULL) !=0)     {        exit(1);    }    if (pthread_create(&tid[1], NULL, thread1, NULL) !=0)     {        exit(1);    }    sleep(5);    printf("in main thread tag 1/n");    pthread_cancel(tid[0]);    pthread_join(tid[0], NULL);    pthread_join(tid[1], NULL);    pthread_mutex_destroy(&mutex);    pthread_cond_destroy(&cond);    return0;}復制代碼

示例代碼_對上述程序的跟蹤

復制代碼
[Thread debugging using libthread_db enabled]Breakpoint 8, main () at testthread.cpp:3434if (pthread_create(&tid[0], NULL, thread0, NULL) !=0) (gdb) bt#0  main () at testthread.cpp:34(gdb) n[New Thread 0xb7fecb70 (LWP 2494)]in thread 0 tag 1Breakpoint 9, main () at testthread.cpp:3838if (pthread_create(&tid[1], NULL, thread1, NULL) !=0) (gdb) bt#0  main () at testthread.cpp:38(gdb) n[Switching to Thread 0xb7fecb70 (LWP 2494)]Breakpoint 1, thread0 (arg=0x0) at testthread.cpp:1313        pthread_cond_wait(&cond, &mutex);(gdb) n[New Thread 0xb77ebb70 (LWP 2495)]in main thread tag 1[Switching to Thread 0xb7fee6d0 (LWP 2491)]Breakpoint 10, main () at testthread.cpp:4444        pthread_cancel(tid[0]);(gdb) nin thread 1 tag 1Breakpoint 11, main () at testthread.cpp:4646        pthread_join(tid[0], NULL);(gdb) n[Switching to Thread 0xb77ebb70 (LWP 2495)]Breakpoint 2, thread1 (arg=0x0) at testthread.cpp:2424        pthread_mutex_lock(&mutex);(gdb) n[Thread 0xb7fecb70 (LWP 2494) exited][Switching to Thread 0xb7fee6d0 (LWP 2491)]Breakpoint 12, main () at testthread.cpp:4747        pthread_join(tid[1], NULL);(gdb) n^CProgram received signal SIGINT, Interrupt.0x00110416in __kernel_vsyscall ()(gdb) info breakNum     Type           Disp Enb Address    What1       breakpoint     keep y   0x08048742in thread0(void*)                                       at testthread.cpp:13    breakpoint already hit 1 time2       breakpoint     keep y   0x080487a4in thread1(void*)                                       at testthread.cpp:24    breakpoint already hit 1 time3       breakpoint     keep y   0x08048762in thread0(void*)                                       at testthread.cpp:154       breakpoint     keep y   0x0804877ain thread0(void*)                                       at testthread.cpp:175       breakpoint     keep y   0x080487bcin thread1(void*)                                       at testthread.cpp:266       breakpoint     keep y   0x080487c8in thread1(void*)                                       at testthread.cpp:277       breakpoint     keep y   0x080487e0in thread1(void*)                                       at testthread.cpp:298       breakpoint     keep y   0x080487f5in main() at testthread.cpp:34    breakpoint already hit 1 time9       breakpoint     keep y   0x0804882ein main() at testthread.cpp:38    breakpoint already hit 1 time10      breakpoint     keep y   0x08048882in main() at testthread.cpp:44    breakpoint already hit 1 time---Type <return> to continue, or q <return> to quit---11      breakpoint     keep y   0x0804888ein main() at testthread.cpp:46    breakpoint already hit 1 time12      breakpoint     keep y   0x080488a2in main() at testthread.cpp:47    breakpoint already hit 1 time13      breakpoint     keep y   0x080488b6in main() at testthread.cpp:49(gdb) 復制代碼

我們發現,

Breakpoint 12, main () at testthread.cpp:47

47 pthread_join(tid[1], NULL);

(gdb) n

^C

一直卡在這里。

看起來似乎沒有什么問題,但是 main 函數調用了一個pthread_cancel 來取消 tid[0] 線程。上面程序編譯后運行時會發生無法終止情況,看起來像是pthread_cancel 將 tid[0] 取消時沒有執行 pthread_mutex_unlock函數,這樣 mutex 就被永遠鎖住,線程tid[1] 也陷入無休止的等待中。事實是這樣嗎?

2. 取消點(Cancellation Point)

要注意的是 pthread_cancel調用并不等待線程終止,它只提出請求。線程在取消請求(pthread_cancel)發出后會繼續運行,直到到達某個取消點(Cancellation Point)。取消點是線程檢查是否被取消并按照請求進行動作的一個位置。pthread_cancel manual 說以下幾個 POSIX 線程函數是取消點:

    pthread_join(3)

pthread_cond_wait(3)

pthread_cond_timedwait(3)

pthread_testcancel(3)

sem_wait(3)

sigwait(3)

以及read()、write()等會引起阻塞的系統調用都是Cancelation-point,而其他pthread函數都不會引起Cancelation動作。

在中間我們可以找到 pthread_cond_wait 就是取消點之一。

但是,令人迷惑不解的是,所有介紹 Cancellation Points的文章都僅僅說,當線程被取消后,將繼續運行到取消點并發生取消動作。但我們注意到上面例子中 pthread_cancel前面 main 函數已經sleep 了 5秒,那么在 pthread_cancel 被調用時,thread0 到底運行到pthread_cond_wait 沒有?

如果 thread0 運行到了pthread_cond_wait,那么照上面的說法,它應該繼續運行到下一個取消點并發生取消動作,而后面并沒有取消點,所以thread0 應該運行到 pthread_exit并結束,這時 mutex 就會被解鎖,這樣就不應該發生死鎖啊。

說明:

從我的GDB中可以看出,運行到pthread_cond_wait這里后,就沒有往下運行了。應該說,這是當前的取消點。

3. 取消類型(Cancellation Type)

我們會發現,通常的說法:某某函數是 Cancellation Points,這種方法是容易令人混淆的。因為函數的執行是一個時間過程,而不是一個時間點。其實真正的Cancellation Points 只是在這些函數中Cancellation Type 被修改為PHREAD_CANCEL_ASYNCHRONOUS 和修改回PTHREAD_CANCEL_DEFERRED 中間的一段時間。

POSIX 的取消類型有兩種,一種是延遲取消(PTHREAD_CANCEL_DEFERRED),這是系統默認的取消類型,即在線程到達取消點之前,不會出現真正的取消;另外一種是異步取消(PHREAD_CANCEL_ASYNCHRONOUS),使用異步取消時,線程可以在任意時間取消。

4. Linux 的取消點實現

下面我們看 Linux 是如何實現取消點的。(其實這個準確點兒應該說是GNU 取消點實現,因為 pthread庫是實現在 glibc 中的。)我們現在在 Linux 下使用的pthread 庫其實被替換成了 NPTL,被包含在 glibc庫中。

以 pthread_cond_wait 為例,glibc-2.6/nptl/pthread_cond_wait.c中:

示例代碼

復制代碼
145/* Enable asynchronous cancellation. Required by the standard. */146 cbuffer.oldtype = __pthread_enable_asynccancel ();147148/* Wait until woken by signal or broadcast. */149 lll_futex_wait (&cond->__data.__futex, futex_val);150151/* Disable asynchronous cancellation. */152 __pthread_disable_asynccancel (cbuffer.oldtype);復制代碼

我們可以看到,在線程進入等待之前,pthread_cond_wait先將線程取消類型設置為異步取消(__pthread_enable_asynccancel),當線程被喚醒時,線程取消類型被修改回延遲取消__pthread_disable_asynccancel 。

這就意味著,所有在 __pthread_enable_asynccancel之前接收到的取消請求都會等待__pthread_enable_asynccancel執行之后進行處理,所有在__pthread_disable_asynccancel之前接收到的請求都會在__pthread_disable_asynccancel 之前被處理,所以真正的Cancellation Point 是在這兩點之間的一段時間。(也就是在__pthread_enable_asynccancel與__pthread_disable_asynccancel間處理取消請求)

5. 對示例函數進入死鎖的解釋

當main函數中調用pthread_cancel 前,thread0已經進入了 pthread_cond_wait函數并將自己列入等待條件的線程列表中(lll_futex_wait)。這個可以通過GDB 在各個函數上設置斷點來驗證。

當 pthread_cancel 被調用時,tid[0]線程仍在等待,取消請求發生在 __pthread_disable_asynccancel前,所以會被立即響應。但是 pthread_cond_wait為注冊了一個線程清理程序(glibc-2.6/nptl/pthread_cond_wait.c):

126 /* Before we block we enable cancellation. Therefore we have to

127 install a cancellation handler. */

128 __pthread_cleanup_push (&buffer, __condvar_cleanup, &cbuffer);

那么這個線程 清理程序 __condvar_cleanup 干了什么事情呢?我們可以注意到在它的實現最后(glibc-2.6/nptl/pthread_cond_wait.c):

85 /* Get the mutex before returning unless asynchronous cancellation

86 is in effect. */

87 __pthread_mutex_cond_lock (cbuffer->mutex);

88}

哦,__condvar_cleanup 在最后將mutex 重新鎖上了。而這時候 thread1 還在休眠(sleep(10)),等它醒來時,mutex將會永遠被鎖住,這就是為什么 thread1陷入無休止的阻塞中。

【可是為什么pthread_cond_wait要在最后上鎖呢?】

6. 如何避免因此產生的死鎖

由于線程清理函數 pthread_cleanup_push 使用的策略是先進后出(FILO),那么我們可以在pthread_cond_wait 函數前先注冊一個線程處理函數:

示例代碼

復制代碼
void cleanup(void*arg){    pthread_mutex_unlock(&mutex);}void* thread0(void* arg){    pthread_cleanup_push(cleanup, NULL); // thread cleanup handler    pthread_mutex_lock(&mutex);    pthread_cond_wait(&cond, &mutex);    pthread_mutex_unlock(&mutex);    pthread_cleanup_pop(0);    pthread_exit(NULL);}復制代碼

這樣,當線程被取消時,先執行 pthread_cond_wait 中注冊的線程清理函數 __condvar_cleanup,將mutex 鎖上,再執行 thread0中注冊的線程處理函數 cleanup,將mutex解鎖。這樣就避免了死鎖的發生。

7. 結論

多線程下的線程同步一直是一個讓人很頭痛的問題。POSIX 為了避免立即取消程序引起的資源占用問題而引入的 Cancellation Points概念是一個非常好的設計,但是不合適的使用 pthread_cancel仍然會引起線程同步的問題。了解POSIX 線程取消點在 Linux 下的實現更有助于理解它的機制和有利于更好的應用這個機制。

8. 參考文獻

[1] W. Richard Stevens, Stephen A. Rago: Advanced Programming in the UNIX Environment, 2nd Edition.

[2] Linux Manpage

http://wzw19191.blog.163.com/blog/static/131135470200992610550684/

[3] http://hi.baidu.com/hackers365/blog/item/412d0f085c1fd18f0a7b8205.html

【4】http://blog.csdn.net/yanook/article/details/6589798


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美成人手机在线| 国内精品国产三级国产在线专| 国产亚洲精品美女久久久| www国产精品视频| 亚洲网在线观看| 高清欧美性猛交xxxx黑人猛交| 国产成人精品午夜| 不用播放器成人网| 国产在线精品成人一区二区三区| 成人在线免费观看视视频| 亚洲视频在线免费看| 亚洲国产精品久久久久秋霞不卡| 亚洲第一网中文字幕| 亚洲男人天堂网站| 亚洲综合视频1区| 欧美激情性做爰免费视频| 国产精品99久久久久久人| 欧美成在线视频| 精品亚洲国产成av人片传媒| 日本sm极度另类视频| 欧美有码在线观看视频| 国产精品aaaa| 中文字幕精品www乱入免费视频| 精品国产乱码久久久久酒店| 精品无人国产偷自产在线| 日韩极品精品视频免费观看| 久久久在线视频| 美女撒尿一区二区三区| 日韩欧美成人区| www日韩欧美| 国产精品永久在线| 亚洲人午夜精品免费| 欧美日韩精品在线播放| 久久久女女女女999久久| 欧美日韩国产中文精品字幕自在自线| 欧美在线视频免费播放| 亚洲自拍av在线| 中文字幕国产日韩| 国产欧美日韩亚洲精品| 国产亚洲精品久久久久动| 亚洲精品久久在线| 丝袜亚洲欧美日韩综合| 中文字幕亚洲国产| 国产日韩欧美夫妻视频在线观看| 久久av资源网站| 狠狠色香婷婷久久亚洲精品| 成人免费视频网址| 国内精品视频久久| 国产精品欧美日韩久久| 亚洲欧美激情精品一区二区| 美日韩在线视频| 国产日韩在线视频| 日本高清视频精品| 亚洲视频axxx| 成人免费在线视频网址| 欧美高清在线播放| 97国产真实伦对白精彩视频8| 国产精品91久久久| 亚洲人成电影在线观看天堂色| 国产欧美久久一区二区| 久久99热这里只有精品国产| 亚洲综合日韩中文字幕v在线| 国产一区二区黄| 日韩中文字幕在线播放| 国产精品精品视频| 91爱爱小视频k| 欧美激情在线观看视频| 麻豆乱码国产一区二区三区| 国产精品偷伦一区二区| 亚洲欧美变态国产另类| 中文字幕在线观看亚洲| 国产美女高潮久久白浆| 欧美性xxxxhd| 日韩最新av在线| 日韩成人高清在线| 日韩欧美精品网站| 亚洲xxxxx| 福利微拍一区二区| 亚洲国产精品久久久久| 美女精品视频一区| 91高潮在线观看| 国产精品久久久久久久久久久久| 77777少妇光屁股久久一区| 欧美性xxxxx极品娇小| 美女精品视频一区| 最好看的2019的中文字幕视频| 欧美精品videossex性护士| 日韩在线视频观看| 亚洲精品视频播放| 成人网欧美在线视频| 亚洲最大激情中文字幕| 国产精品一区av| 日韩视频亚洲视频| 国产精品扒开腿做爽爽爽视频| 亚洲精品日韩欧美| 欧美理论电影在线播放| 亚洲男人的天堂在线| 欧美日韩视频在线| 亚洲日韩中文字幕在线播放| 九九久久综合网站| 福利视频第一区| 日韩欧美在线观看| 国产日韩欧美一二三区| 777777777亚洲妇女| 亚洲欧美中文日韩在线| 久久精品亚洲国产| 97国产精品视频人人做人人爱| 一本色道久久88综合日韩精品| 亚洲欧美国产精品久久久久久久| 中文字幕精品在线视频| 久久激情五月丁香伊人| 国产91精品高潮白浆喷水| 51色欧美片视频在线观看| 久久久在线免费观看| 综合国产在线观看| 欧美亚洲视频一区二区| 97人人爽人人喊人人模波多| 最新国产精品亚洲| 中文字幕亚洲二区| 久久久久久这里只有精品| 日韩免费在线视频| 91精品国产综合久久久久久久久| 久久国产精品久久精品| 日本国产高清不卡| 欧美成人免费全部观看天天性色| 中文字幕久久久av一区| 国产精品久久中文| 精品久久久久久久久久久| 国产精品第2页| 国产成人精品免费久久久久| 亚洲精品久久在线| 欧美裸体视频网站| 日韩免费在线免费观看| 亚洲视频在线观看| 亚洲国产精品推荐| 亚洲第一天堂无码专区| 亚洲一级免费视频| 国产成人自拍视频在线观看| 欧美—级a级欧美特级ar全黄| 日韩亚洲一区二区| 欧美大尺度激情区在线播放| 亚洲精品欧美日韩专区| 欧美午夜精品久久久久久久| 亚洲第一页在线| 国产精品1区2区在线观看| 久久久久久免费精品| 亚洲国产精品久久| 91精品国产自产在线观看永久| 欧美视频免费在线| 亚洲国产欧美自拍| 亚洲国产成人精品一区二区| 中文字幕亚洲欧美一区二区三区| 精品久久久久久国产91| 午夜精品久久久久久久久久久久| 51久久精品夜色国产麻豆| 国产日韩欧美电影在线观看| xxx成人少妇69| 国产亚洲精品久久久久久牛牛| 亚洲成人久久网| 992tv成人免费影院| 亚洲欧美激情另类校园| 精品久久久久久久中文字幕| 午夜精品一区二区三区在线视频| 国产精品福利在线观看网址|