POSIX 線程的創建與退出
前言
創建線程:
pthread_create()
退出線程:
pthread_exit()return pthread_cancel()
線程的創建
使用多線程,首先就需要創建一個新線程。那麽線程是如何被創建的呢,是用下面這個函數創建的。
#include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); //Compile and link with -pthread
創建函數的四個參數的意義分別如下:
thread :用來返回新創建的線程的 ID,這個 ID 就像身份證一樣,指定了這個線程,可以用來在隨後的線程交互中使用。
attr : 這個參數是一個 pthread_attr_t 結構體的指針,用來在線程創建的時候指定新線程的屬性。如果在創建線程時,這個參數指定為 NULL, 那麽就會使用默認屬性。
start_routine :這個就是新線程的入口函數,當新線程創建完成後,就從這裏開始執行。
arg :arg 參數就是要傳遞給 start_routine 的參數。
返回值:如果函數執行成功,則返回 0,如果執行失敗,則返回一個錯誤碼。
錯誤碼:
EAGAIN :資源不足以用來創建一個新的線程,或者是達到了系統對線程數量的限制,請參考 setrlimit(2) 和 /proc/sys/kernel/threads-max EINVAL :不可用的 attr EPERM :沒有權限設置 attr 中的一下屬性或者執行時序策略。
下面就是調用 pthread_create() 函數創建線程的一個例子:
#include <stdio.h> #include <pthread.h> #include <errno.h> void * thread_start(void*arg) { if(NULL == arg) { printf("[%u] : arg is NULL\n", (unsigned int)pthread_self()); return NULL; } char * p = (char*)arg; printf("[%u] : arg = [%s]\n", (unsigned int)pthread_self(), p); return NULL; } int main() { pthread_t pt; int errn = pthread_create( &pt, //用來返回新創建的線程的 ID NULL, //使用默認的線程屬性 thread_start,//新線程從這個函數開始執行 "hello"); //傳遞給新創建的線程的參數 if(0 != errn) { printf("error happend when create pthread, errno = [%d]\n", errn); if(EAGAIN == errn) { printf("Insufficient resources\n"); } else if (EINVAL == errn) { printf("Invalid settings in attr\n"); } else if (EPERM == errn) { printf("No permission\n"); } else { printf("An error number that unexpected [%d], when create pthread\n", errn); } return -1; } else { printf("create thread success, threadid : [%u]\n", (unsigned int)pt); } void *r = NULL; errn = pthread_join(pt, &r); if(0 != errn) { printf("error happend when join, errno = [%d]\n", errn); if(EDEADLK == errn) { printf("A deadlock was detected; or thread specifies the calling thread\n"); } else if (EINVAL == errn) { printf("thread is not a joinable thread, or Another thread is already waiting to join with this thread\n"); } else if (ESRCH == errn) { printf("No thread with the ID thread could be found\n"); } else { printf("An error number that unexpected [%d], when join\n", errn); } return -1; } else { printf("thread [%u] over\n", (unsigned int)pt); } return 0; }
接下來編譯並運行,看看結果:
gcc -g -c -o pthread_create.o pthread_create.c -Wall -I./ gcc -g -o pthread_create pthread_create.o -Wall -I./ -lpthread create thread success, threadid : [1243236096] [1243236096] : arg = [hello] thread [1243236096] over
看起來執行成功了。下面再來看看一個線程的退出過程。
線程的退出
從上面的例子中,我們也可以看出,線程的入口,也就是一個函數,函數可以使用 return 進行退出, 那麽在線程中,也是通過 return 進行退出的嗎? 答案是,可以使用 return ,但是如果希望線程在退出的時候, 能夠執行更多的動作,就不能使用 return 直接退出了,那麽該怎樣退出呢,可以使用 pthread_exit() 函數, 或者使用pthread_cancel() 函數。
這兩個函數的原型如下:
#include <pthread.h> int pthread_cancel(pthread_t thread); //向指定的線程發送取消請求 void pthread_exit(void *retval); //結束調用者線程 //Compile and link with -pthread
使用 pthread_exit() 退出線程
pthread_exit() 函數會結束當前進程。如果當前線程是可以被 join 的,則會通過參數 retval 返回一個值給同一個進程裏面的另一個使用pthread_join(3) 函數的線程。
所有使用pthread_cleanup_push(3)函數壓入棧的清理函數,都會被彈出並調用, 調用順序是入棧時的反向順序。如果線程有什麽特別指定的數據,那麽在所有的清理函數執行結束後, 會有適當的函數被調用,來析構這些數據,調用順序不固定。
當一個線程終止後,進程內共享的資源(例如互斥信號量、條件變量、信號量以及文件描述符) 不會被釋放。並且使用atexit(3)函數註冊的函數也不會被調用。
當進程內的最後一個線程終止後,進程也就終止了,就像調用了exit(3)函數一樣,並且參數是0. 這時候,進程內的共享資源就會被釋放,並且使用 atexit(3) 函數註冊的函數, 也會被調用。
下面來看一下 pthread_exit() 函數的一個例子:
#include <stdio.h> #include <pthread.h> void handlers(void *arg) { if(NULL != arg) { printf("%s() : [%s]\n", __func__, (char*)arg); } else { printf("%s()\n", __func__); } } void * thread_start(void *arg) { printf("hello, this is thrad [%u]\n", (unsigned int)pthread_self()); pthread_cleanup_push(handlers, "one"); pthread_cleanup_push(handlers, "two"); pthread_cleanup_push(handlers, "three"); //註意,這裏執行了 pthread_exit() 函數 pthread_exit("he~he~"); pthread_cleanup_pop(1); pthread_cleanup_pop(2); pthread_cleanup_pop(3); return NULL; } int main() { pthread_t pt; int errn = pthread_create(&pt, NULL, thread_start, NULL); if(0 != errn) { printf("error [%d], when create pthread\n", errn); return -1; } else { printf("create thread success, threadid : [%u]\n", (unsigned int)pt); } void *r = NULL; errn = pthread_join(pt, &r); if(0 != errn) { printf("error happend when join, errno = [%d]\n", errn); return -1; } else { printf("thread [%u] over\n", (unsigned int)pt); } if(NULL != r) { printf("thread return : [%s]\n", (const char*)r); } return 0; }
編譯並運行:
#include <stdio.h> #include <pthread.h> void handlers(void *arg) { if(NULL != arg) { printf("%s() : [%s]\n", __func__, (char*)arg); } else { printf("%s()\n", __func__); } } void * thread_start(void *arg) { printf("hello, this is thrad [%u]\n", (unsigned int)pthread_self()); pthread_cleanup_push(handlers, "one"); pthread_cleanup_push(handlers, "two"); pthread_cleanup_push(handlers, "three"); //註意,這裏執行了 pthread_exit() 函數 pthread_exit("he~he~"); pthread_cleanup_pop(1); pthread_cleanup_pop(2); pthread_cleanup_pop(3); return NULL; } int main() { pthread_t pt; int errn = pthread_create(&pt, NULL, thread_start, NULL); if(0 != errn) { printf("error [%d], when create pthread\n", errn); return -1; } else { printf("create thread success, threadid : [%u]\n", (unsigned int)pt); } void *r = NULL; errn = pthread_join(pt, &r); if(0 != errn) { printf("error happend when join, errno = [%d]\n", errn); return -1; } else { printf("thread [%u] over\n", (unsigned int)pt); } if(NULL != r) { printf("thread return : [%s]\n", (const char*)r); } return 0; }
使用 return 退出線程
先來看一個使用 return 退出線程的例子:
#include <stdio.h> #include <pthread.h> void * thread_start(void *arg) { printf("hello, this is thread [%u]\n", (unsigned int)pthread_self()); return "ok"; } int main() { pthread_t pt; int errn = pthread_create(&pt, NULL, thread_start, NULL); if(0 != errn) { printf("error [%d], when create pthread\n", errn); return -1; } else { printf("create thread success, threadid : [%u]\n", (unsigned int)pt); } void *r = NULL; errn = pthread_join(pt, &r); if(0 != errn) { printf("error happend when join, errno = [%d]\n", errn); return -1; } else { printf("thread [%u] over\n", (unsigned int)pt); } if(NULL != r) { printf("thread return : [%s]\n", (const char*)r); } return 0; }
編譯並運行:
gcc -g -c -o pthread-return.o pthread-return.c -Wall -I./ gcc -g -o pthread-return pthread-return.o -Wall -I./ -lpthread ./pthread-return create thread success, threadid : [722429696] //主線程打印的信息 hello, this is thread [722429696] //新創建的線程打印的信息 thread [722429696] over //主線程打印的信息 thread return : [ok] //主線程打印的信息,其中[ok]為新創建的線程打印的信息
既然 return 和 pthread_exit() 函數都是結束線程,並返回數據,那麽它們之間的區別是什麽呢?
區別就在於,使用 return 退出線程的時候,不會執行線程使用 pthread_cleanup_push(3) 註冊的清理函數。 可以再寫一個例子,看看效果。
#include <stdio.h> #include <pthread.h> void handlers(void *arg) { if(NULL != arg) { printf("%s() : [%s]\n", __func__, (char*)arg); } else { printf("%s()\n", __func__); } } void * thread_start(void *arg) { printf("hello, this is thrad [%u]\n", (unsigned int)pthread_self()); pthread_cleanup_push(handlers, "one"); pthread_cleanup_push(handlers, "two"); pthread_cleanup_push(handlers, "three"); //註意,這裏執行了 return return "he~he~"; pthread_cleanup_pop(1); pthread_cleanup_pop(2); pthread_cleanup_pop(3); return "ok"; } int main() { pthread_t pt; int errn = pthread_create(&pt, NULL, thread_start, NULL); if(0 != errn) { printf("error [%d], when create pthread\n", errn); return -1; } else { printf("create thread success, threadid : [%u]\n", (unsigned int)pt); } void *r = NULL; errn = pthread_join(pt, &r); if(0 != errn) { printf("error happend when join, errno = [%d]\n", errn); return -1; } else { printf("thread [%u] over\n", (unsigned int)pt); } if(NULL != r) { printf("thread return : [%s]\n", (const char*)r); } return 0; }
編譯並運行:
gcc -g -c -o pthread-return.o pthread-return.c -Wall -I./ gcc -g -o pthread-return pthread-return.o -Wall -I./ -lpthread ./pthread-return create thread success, threadid : [4185097984] hello, this is thrad [4185097984] thread [4185097984] over thread return : [he~he~]
可以看出,確實沒有執行清理函數,為什麽呢?
因為pthread_cleanup_push(3) 和 pthread_cleanup_pop() 是使用宏實現的。 在 pthread_cleanup_push() 和 pthread_cleanup_pop() 之間,是一個大個的 do{}while(0), 遇到 return 當然就直接退出啦。 具體的實現方式請看這裏, 因為本文只講述一下線程的創建和退出, 所以 pthread_cleanup_push 和 pthread_cleanup_pop 的說明放在其它地方了。
使用 pthread_cancel() 退出線程
先看一下函數原型:
#include <pthread.h> int pthread_cancel(pthread_t thread); //Compile and link with -pthread.
其中的 thread 參數就是目的線程的線程ID
pthread_cancel() 函數會給 thread 指定的線程發送一個取消請求。 至於目標線程是否以及合適對這個請求進行反應,則視目標線程的兩個屬性而定: 取消屬性的 state 和 type
一個線程的取消屬性的 state 由 pthread_setcancelstate(3) 函數來設置, 可以是 enabled (一個新創建的線程的默認方式就是 enabled)或者 disabled。 如果一個線程的取消屬性設置了 disabled ,那麽對著個線程發送的取消請求會一直存在, 直到線程恢復了取消屬性的設置。如果一個線程的取消屬性設置了 enabled , 那麽取消屬性的 type 就由取消消息什麽什麽時候到來而決定了。
一個線程的取消類型(type)由 pthread_setcanceltype(3) 函數來設置。 可以是異步的,也可以是延緩的。異步取消屬性的意味著線程任何時間都可以被取消 (通常是立即被取消,但操作系統不保證這一點)。延緩取消是說,取消操作會被延遲, 直到線程接下來的調用的函數是個取消點。在 pthreads(7) (Linux 命令行中執行 man 7 pthreads) 中列出的函數就是或者是取消點。
當一個取消請求起作用時,下面的步驟會按順序發生。
- 取消清理函數會被出棧並被執行。
- 線程相關數據會被析構,順序不確定。
- 線程終止。
以上的步驟會異步的執行,pthread_cancel() 函數的返回狀態會指出取消請求是否成功的發給了制定的線程。
在一個被取消的線程終止後,使用 pthread_join(3) 函數 join 時,會得到線程的結束狀態為 PTHREAD_CANCELED 。 join 一個線程是知道這個取消操作是否完成的唯一方法。
下面是 man pthread_cancel 手冊中的一段示例代碼:
#include <pthread.h> #include <stdio.h> #include <errno.h> #include <stdlib.h> #include <unistd.h> #define handle_error_en(en, msg) do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0) static void * thread_func(void *ignored_argument) { int s; /* Disable cancellation for a while, so that we don‘t * immediately react to a cancellation request */ s = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); if (s != 0) handle_error_en(s, "pthread_setcancelstate"); printf("thread_func(): started; cancellation disabled\n"); sleep(5); printf("thread_func(): about to enable cancellation\n"); s = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); if (s != 0) handle_error_en(s, "pthread_setcancelstate"); /* sleep() is a cancellation point */ sleep(1000); /* Should get canceled while we sleep */ /* Should never get here */ printf("thread_func(): not canceled!\n"); return NULL; } int main(void) { pthread_t thr; void *res; int s; /* Start a thread and then send it a cancellation request */ s = pthread_create(&thr, NULL, &thread_func, NULL); if (s != 0) handle_error_en(s, "pthread_create"); sleep(2); /* Give thread a chance to get started */ printf("main(): sending cancellation request\n"); s = pthread_cancel(thr); if (s != 0) handle_error_en(s, "pthread_cancel"); /* Join with thread to see what its exit status was */ s = pthread_join(thr, &res); if (s != 0) handle_error_en(s, "pthread_join"); if (res == PTHREAD_CANCELED) printf("main(): thread was canceled\n"); else printf("main(): thread wasn‘t canceled (shouldn‘t happen!)\n"); exit(EXIT_SUCCESS); }
編譯並運行 :
./pthread_cancel
thread_func(): started; cancellation disabled
main(): sending cancellation request
thread_func(): about to enable cancellation
main(): thread was canceled
同步地址:https://www.fengbohello.top/archives/linux-pthread-lifecycle
POSIX 線程的創建與退出