C語言如何正確的終止正在運(yùn)行的子線程
最近開發(fā)一些東西,線程數(shù)非常之多,當(dāng)用戶輸入Ctrl+C的情形下,默認(rèn)的信號處理會把程序退出,這時(shí)有可能會有很多線程的資源沒有得到很好的釋放,造成了內(nèi)存泄露等等諸如此類的問題,本文就是圍繞著這么一個(gè)使用場景討論如何正確的終止正在運(yùn)行的子線程。其實(shí)本文更確切的說是解決如何從待終止線程外部安全的終止正在運(yùn)行的線程
首先我們來看一下,讓當(dāng)前正在運(yùn)行的子線程停止的所有方法
1.任何一個(gè)線程調(diào)用exit
2.pthread_exit
3.pthread_kill
4.pthread_cancel
下面我們一一分析各種終止正在運(yùn)行的程序的方法
任何一個(gè)線程調(diào)用exit
任何一個(gè)線程只要調(diào)用了exit都會導(dǎo)致進(jìn)程結(jié)束,各種子線程當(dāng)然也能很好的結(jié)束了,可是這種退出會有一個(gè)資源釋放的問題.我們知道當(dāng)一個(gè)進(jìn)程終止時(shí),內(nèi)核對該進(jìn)程所有尚未關(guān)閉的文件描述符調(diào)用close關(guān)閉,所以即使用戶程序不調(diào)用close,在終止時(shí)內(nèi)核也會自動(dòng)關(guān)閉它打開的所有文件。沒錯(cuò),標(biāo)準(zhǔn)C++ IO流也會很好的在exit退出時(shí)得到flush并且釋放資源,這些東西并不會造成資源的浪費(fèi)(系統(tǒng)調(diào)用main函數(shù)入口類似于exit(main(argc,argv))).表面上似乎所有的問題都能隨著進(jìn)程的結(jié)束來得到很好的處理,其實(shí)并不然,我們程序從堆上分配的內(nèi)存就不能得到很好的釋放,如new ,delete后的存儲空間,這些空間進(jìn)程結(jié)束并不會幫你把這部分內(nèi)存歸還給內(nèi)存.(本文初稿時(shí),因基礎(chǔ)不牢固,此處寫錯(cuò),事實(shí)上無論進(jìn)程這樣結(jié)束,系統(tǒng)都將會釋放掉所有代碼所申請的資源,無論是堆上的還是棧上的。(感謝ZKey的指導(dǎo))。這種結(jié)束所有線程(包括主線程)的方式實(shí)際上在很多時(shí)候是非??扇〉?,但是對于針對關(guān)閉時(shí)進(jìn)行一些別的邏輯的處理(指非資源釋放邏輯)就不會很好,例如我想在程序被kill掉之前統(tǒng)計(jì)一下完成了多少的工作,這個(gè)統(tǒng)計(jì)類似于MapReduce,需要去每個(gè)線程獲取,并且最后歸并程一個(gè)統(tǒng)一的結(jié)果等等場景)
pthread_exit
此函數(shù)的使用場景是當(dāng)前運(yùn)行的線程運(yùn)行pthread_exit得到退出,對于各個(gè)子線程能夠清楚地知道自己在什么時(shí)候結(jié)束的情景下,非常好用,可是實(shí)際上往往很多時(shí)候一個(gè)線程不能知道知道在什么時(shí)候該結(jié)束,例如遭遇Ctrl+C時(shí),kill進(jìn)程時(shí),當(dāng)然如果排除所有的外界干擾的話,那就讓每個(gè)線程干完自己的事情后,然后自覺地乖乖的調(diào)用pthread_exit就可以了,這并不是本文需要討論的內(nèi)容,本文的情景就是討論如何處理特殊情況。
這里還有一種方法,既然子線程可以通過pthread_exit來正確退出,那么我們可以在遭遇Ctrl+C時(shí),kill進(jìn)程時(shí)處理signal信號,然后分別給在某一個(gè)線程可以訪問的公共區(qū)域存上一個(gè)flag變量,線程內(nèi)部每運(yùn)行一段時(shí)間(很短)來檢查一下flag,若發(fā)現(xiàn)需要終止自己時(shí),自己調(diào)用pthread_exit,此法有一個(gè)弱點(diǎn)就是當(dāng)子線程需要進(jìn)行阻塞的操作時(shí),可能無暇顧及檢查flag,例如socket阻塞操作。如果你的子線程的任務(wù)基本沒有非阻塞的函數(shù),那么這么干也不失為一種很好的方案。
pthread_kill
不要被這個(gè)可怕的邪惡的名字所嚇倒,其實(shí)pthread_kill并不像他的名字那樣威力大,使用之后,你會感覺,他徒有虛名而已
pthread_kill的職責(zé)其實(shí)只是向指定的線程發(fā)送signal信號而已,并沒有真正的kill掉一個(gè)線程,當(dāng)然這里需要說明一下,有些信號的默認(rèn)行為就是exit,那此時(shí)你使用pthread_kill發(fā)送信號給目標(biāo)線程,目標(biāo)線程會根據(jù)這個(gè)信號的默認(rèn)行為進(jìn)行操作,有可能是exit。當(dāng)然我們同時(shí)也可以更改獲取某個(gè)信號的行為,以此來達(dá)到我們終止子線程的目的。
#define _MULTI_THREADED
#include <pthread.h>
#include <stdio.h>
#include <signal.h>
#include "check.h"
#define NUMTHREADS 3
void sighand(int signo);
void *threadfunc(void *parm)
{
pthread_t self = pthread_self();
pthread_id_np_t tid;
int rc;
pthread_getunique_np(&self, &tid);
printf("Thread 0x%.8x %.8x entered\n", tid);
errno = 0;
rc = sleep(30);
if (rc != 0 && errno == EINTR) {
printf("Thread 0x%.8x %.8x got a signal delivered to it\n",
tid);
return NULL;
}
printf("Thread 0x%.8x %.8x did not get expected results! rc=%d, errno=%d\n",
tid, rc, errno);
return NULL;
}
int main(int argc, char **argv)
{
int rc;
int i;
struct sigaction actions;
pthread_t threads[NUMTHREADS];
printf("Enter Testcase - %s\n", argv[0]);
printf("Set up the alarm handler for the process\n");
memset(&actions, 0, sizeof(actions));
sigemptyset(&actions.sa_mask);
actions.sa_flags = 0;
actions.sa_handler = sighand;
rc = sigaction(SIGALRM,&actions,NULL);
checkResults("sigaction\n", rc);
for(i=0; i<NUMTHREADS; ++i) {
rc = pthread_create(&threads[i], NULL, threadfunc, NULL);
checkResults("pthread_create()\n", rc);
}
sleep(3);
for(i=0; i<NUMTHREADS; ++i) {
rc = pthread_kill(threads[i], SIGALRM);
checkResults("pthread_kill()\n", rc);
}
for(i=0; i<NUMTHREADS; ++i) {
rc = pthread_join(threads[i], NULL);
checkResults("pthread_join()\n", rc);
}
printf("Main completed\n");
return 0;
}
void sighand(int signo)
{
pthread_t self = pthread_self();
pthread_id_np_t tid;
pthread_getunique_np(&self, &tid);
printf("Thread 0x%.8x %.8x in signal handler\n",
tid);
return;
}
運(yùn)行輸出為:
Output:
Enter Testcase - QP0WTEST/TPKILL0
Set up the alarm handler for the process
Thread 0x00000000 0000000c entered
Thread 0x00000000 0000000d entered
Thread 0x00000000 0000000e entered
Thread 0x00000000 0000000c in signal handler
Thread 0x00000000 0000000c got a signal delivered to it
Thread 0x00000000 0000000d in signal handler
Thread 0x00000000 0000000d got a signal delivered to it
Thread 0x00000000 0000000e in signal handler
Thread 0x00000000 0000000e got a signal delivered to it
Main completed
我們可以通過截獲的signal信號,來釋放掉線程申請的資源,可是遺憾的是我們不能再signal處理里調(diào)用pthread_exit來終結(jié)掉線程,因?yàn)閜thread_exit是中介當(dāng)前線程,而signal被調(diào)用的方式可以理解為內(nèi)核的回調(diào),不是在同一個(gè)線程運(yùn)行的,所以這里只能做處理釋放資源的事情,線程內(nèi)部只有判斷有沒有被中斷(一般是EINTR)來斷定是否要求自己結(jié)束,判定后可以調(diào)用pthread_exit退出。
此法對于一般的操作也是非常可行的,可是在有的情況下就不是一個(gè)比較好的方法了,比如我們有一些線程在處理網(wǎng)絡(luò)IO事件,假設(shè)它是一種一個(gè)客戶端對應(yīng)一個(gè)服務(wù)器線程,阻塞從Socket中讀消息的情況。我們一般在網(wǎng)絡(luò)IO的庫里面回家上對EINTR信號的處理,例如recv時(shí)發(fā)現(xiàn)返回值小于0,檢查error后,會進(jìn)行他對應(yīng)的操作。有可能他會再recv一次,那就相當(dāng)于我的線程根本就不回終止,因?yàn)榫W(wǎng)絡(luò)IO的類有可能不知道在獲取EINTR時(shí)要終止線程。也就是說這不是一個(gè)特別好的可移植方案,如果你線程里的操作使用了很多外來的不太熟悉的類,而且你并不是他對EINTR的處理手段是什么,這是你在使用這樣的方法來終止就有可能出問題了。而且如果你不是特別熟悉這方面的話你會很苦惱,“為什么我的測試代碼全是ok的,一加入你們部門開發(fā)的框架進(jìn)來就不ok了,肯定是你們框架出問題了”。好了,為了不必要的麻煩,我最后沒有使用這個(gè)方案。
pthread_cancel
這個(gè)方案是我最終采用的方案,我認(rèn)為是解決這個(gè)問題,通用的最好的解決方案,雖然前面其他方案的有些問題他可能也不好解決,但是相比較而言,還是相當(dāng)不錯(cuò)的
pthread_cancel可以單獨(dú)使用,因?yàn)樵诤芏嘞到y(tǒng)函數(shù)里面本身就有很多的斷點(diǎn),當(dāng)調(diào)用這些系統(tǒng)函數(shù)時(shí)就會命中其內(nèi)部的斷點(diǎn)來結(jié)束線程,如下面的代碼中,即便注釋掉我們自己設(shè)置的斷點(diǎn)pthread_testcancel()程序還是一樣的會被成功的cancel掉,因?yàn)閜rintf函數(shù)內(nèi)部有取消點(diǎn)(如果大家想了解更多的函數(shù)的取消點(diǎn)情況,可以閱讀《Unix高級環(huán)境編程》的線程部分)
#include <pthread.h>
#include <stdio.h>
#include<stdlib.h>
#include <unistd.h>
void *threadfunc(void *parm)
{
printf("Entered secondary thread\n");
while (1) {
printf("Secondary thread is looping\n");
pthread_testcancel();
sleep(1);
}
return NULL;
}
int main(int argc, char **argv)
{
pthread_t thread;
int rc=0;
printf("Entering testcase\n");
/* Create a thread using default attributes */
printf("Create thread using the NULL attributes\n");
rc = pthread_create(&thread, NULL, threadfunc, NULL);
checkResults("pthread_create(NULL)\n", rc);
/* sleep() is not a very robust way to wait for the thread */
sleep(1);
printf("Cancel the thread\n");
rc = pthread_cancel(thread);
checkResults("pthread_cancel()\n", rc);
/* sleep() is not a very robust way to wait for the thread */
sleep(10);
printf("Main completed\n");
return 0;
}
輸出:
Entering testcase
Create thread using the NULL attributes
Entered secondary thread
Secondary thread is looping
Cancel the thread
Main completed
POSIX保證了絕大部分的系統(tǒng)調(diào)用函數(shù)內(nèi)部有取消點(diǎn),我們看到很多在cancel調(diào)用的情景下,recv和send函數(shù)最后都會設(shè)置pthread_testcancel()取消點(diǎn),其實(shí)這不是那么有必要的,那么究竟什么時(shí)候該pthread_testcancel()出場呢?《Unix高級環(huán)境編程》也說了,當(dāng)遇到大量的基礎(chǔ)計(jì)算時(shí)(如科學(xué)計(jì)算),需要自己來設(shè)置取消點(diǎn)。
ok,得益于pthread_cancel,我們很輕松的把線程可以cancel掉,可是我們的資源呢?何時(shí)釋放...
下面來看兩個(gè)pthread函數(shù)
1.void pthread_cleanup_push(void (*routine)(void *), void *arg);
2.void pthread_cleanup_pop(int execute);
這兩個(gè)函數(shù)能夠保證在 1函數(shù)調(diào)用之后,2函數(shù)調(diào)用之前的任何形式的線程結(jié)束調(diào)用向pthread_cleanup_push注冊的回調(diào)函數(shù)
另外我們還可通過下面這個(gè)函數(shù)來設(shè)置一些狀態(tài)
int pthread_setcanceltype(int type, int *oldtype);
| Cancelability | Cancelability State | Cancelability Type |
|---|---|---|
| disabled | PTHREAD_CANCEL_DISABLE | PTHREAD_CANCEL_DEFERRED |
| disabled | PTHREAD_CANCEL_DISABLE | PTHREAD_CANCEL_ASYNCHRONOUS |
| deferred | PTHREAD_CANCEL_ENABLE | PTHREAD_CANCEL_DEFERRED |
| asynchronous | PTHREAD_CANCEL_ENABLE | PTHREAD_CANCEL_ASYNCHRONOUS |
當(dāng)我們設(shè)置type為PTHREAD_CANCEL_ASYNCHRONOUS時(shí),線程并不會等待命中取消點(diǎn)才結(jié)束,而是立馬結(jié)束
好了,下面貼代碼:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int footprint=0;
char *storage;
void freerc(void *s)
{
free(s);
puts("the free called");
}
static void checkResults(char *string, int rc) {
if (rc) {
printf("Error on : %s, rc=%d",
string, rc);
exit(EXIT_FAILURE);
}
return;
}
void *thread(void *arg) {
int rc=0, oldState=0;
rc = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); //close the cancel switch
checkResults("pthread_setcancelstate()\n", rc);
if ((storage = (char*) malloc(80)) == NULL) {
perror("malloc() failed");
exit(6);
}
rc = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,&oldState); //open the cancel switch
checkResults("pthread_setcancelstate(2)\n", rc);
/* Plan to release storage even if thread doesn't exit normally */
pthread_cleanup_push(freerc, storage); /*the free is method here you can use your own method here*/
puts("thread has obtained storage and is waiting to be cancelled");
footprint++;
while (1)
{
pthread_testcancel(); //make a break point here
//pthread_exit(NULL); //test exit to exam whether the freerc method called
sleep(1);
}
pthread_cleanup_pop(1);
}
main() {
pthread_t thid;
void *status=NULL;
if (pthread_create(&thid, NULL, thread, NULL) != 0) {
perror("pthread_create() error");
exit(1);
}
while (footprint == 0)
sleep(1);
puts("IPT is cancelling thread");
if (pthread_cancel(thid) != 0) {
perror("pthread_cancel() error");
sleep(2);
exit(3);
}
if (pthread_join(thid, &status) != 0) {
if(status != PTHREAD_CANCELED){
perror("pthread_join() error");
exit(4);
}
}
if(status == PTHREAD_CANCELED)
puts("PTHREAD_CANCELED");
puts("main exit");
}
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
深度解析三個(gè)常見的C語言內(nèi)存函數(shù)
這篇文章主要深度解析了三個(gè)常見的C語言內(nèi)存函數(shù)memcpy,memmove,memcmp,所以本文將對memcpy,memmove,memcmp 三個(gè)函數(shù)進(jìn)行詳解和模擬實(shí)現(xiàn),需要的朋友可以參考下2023-07-07
C語言二叉樹的三種遍歷方式的實(shí)現(xiàn)及原理
這篇文章主要介紹了C語言二叉樹的三種遍歷方式的實(shí)現(xiàn)及原理,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07
Vscode搭建遠(yuǎn)程c開發(fā)環(huán)境的圖文教程
很久沒有寫C語言了,今天抽空學(xué)習(xí)下C語言知識,接下來通過本文給大家介紹Vscode搭建遠(yuǎn)程c開發(fā)環(huán)境的詳細(xì)步驟,本文通過圖文實(shí)例代碼相結(jié)合給大家介紹的非常詳細(xì),需要的朋友參考下吧2021-11-11
C語言實(shí)現(xiàn)個(gè)人財(cái)務(wù)管理
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)個(gè)人財(cái)務(wù)管理,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
C++設(shè)計(jì)與實(shí)現(xiàn)ORM系統(tǒng)實(shí)例詳解
這篇文章主要為大家介紹了C++設(shè)計(jì)與實(shí)現(xiàn)ORM系統(tǒng)實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
c++實(shí)現(xiàn)加載so動(dòng)態(tài)庫中的資源
下面小編就為大家?guī)硪黄猚++實(shí)現(xiàn)加載so動(dòng)態(tài)庫中的資源。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-12-12
利用C++的基本算法實(shí)現(xiàn)十個(gè)數(shù)排序
以下是對利用C++的基本算法實(shí)現(xiàn)十個(gè)數(shù)排序的代碼進(jìn)行了介紹,需要的朋友可以過來參考下,希望對大家有所幫助2013-10-10
C語言示例講解動(dòng)態(tài)/文件/靜態(tài)功能版本的通訊錄實(shí)現(xiàn)
通訊錄是一個(gè)可以記錄親人、好友信息的工具,這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)通訊錄管理,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07

