#include <pthread.h> int pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine)(void*), void *restrict arg);
返回值:成功返回0,失敗返回錯誤號。以前學過的系統函數都是成功返回0,失敗返回-1,而錯誤號保存在全局變數errno
中,而pthread庫的函數都是通過返回值返回錯誤號,雖然每個綫程也都有一個errno
,但這是為了兼容其它函數介面而提供的,pthread庫本身並不使用它,通過返回值返回錯誤碼更加清晰。
在一個綫程中調用pthread_create()創建新的綫程後,當前線程從pthread_create()返回繼續往下執行,而新的綫程所執行的代碼由我們傳給pthread_create
的函數指針start_routine
決定。start_routine
函數接收一個參數,是通過pthread_create
的arg
參數傳遞給它的,該參數的類型為void *
,這個指針按什麼類型解釋由調用者自己定義。start_routine
的返回值類型也是void *
,這個指針的含義同樣由調用者自己定義。start_routine
返回時,這個綫程就退出了,其它綫程可以調用pthread_join
得到start_routine
的返回值,類似於父進程調用wait(2)
得到子進程的退出狀態,稍後詳細介紹pthread_join
。
pthread_create
成功返回後,新創建的綫程的id被填寫到thread
參數所指向的內存單元。我們知道進程id的類型是pid_t
,每個進程的id在整個系統中是唯一的,調用getpid(2)
可以獲得當前進程的id,是一個正整數值。綫程id的類型是thread_t
,它只在當前進程中保證是唯一的,在不同的系統中thread_t
這個類型有不同的實現,它可能是一個整數值,也可能是一個結構體,也可能是一個地址,所以不能簡單地當成整數用printf
打印,調用pthread_self(3)
可以獲得當前線程的id。
attr
參數表示綫程屬性,本章不深入討論綫程屬性,所有代碼例子都傳NULL
給attr
參數,表示綫程屬性取預設值,感興趣的讀者可以參考[APUE2e]。首先看一個簡單的例子:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> pthread_t ntid; void printids(const char *s) { pid_t pid; pthread_t tid; pid = getpid(); tid = pthread_self(); printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int)pid, (unsigned int)tid, (unsigned int)tid); } void *thr_fn(void *arg) { printids(arg); return NULL; } int main(void) { int err; err = pthread_create(&ntid, NULL, thr_fn, "new thread: "); if (err != 0) { fprintf(stderr, "can't create thread: %s\n", strerror(err)); exit(1); } printids("main thread:"); sleep(1); return 0; }
編譯運行結果如下:
$ gcc main.c -lpthread $ ./a.out main thread: pid 7398 tid 3084450496 (0xb7d8fac0) new thread: pid 7398 tid 3084446608 (0xb7d8eb90)
可知在Linux上,thread_t
類型是一個地址值,屬於同一進程的多個綫程調用getpid(2)
可以得到相同的進程號,而調用pthread_self(3)
得到的綫程號各不相同。
由於pthread_create
的錯誤碼不保存在errno
中,因此不能直接用perror(3)
打印錯誤信息,可以先用strerror(3)
把錯誤碼轉換成錯誤信息再打印。
如果任意一個綫程調用了exit
或_exit
,則整個進程的所有線程都終止,由於從main
函數return
也相當於調用exit
,為了防止新創建的綫程還沒有得到執行就終止,我們在main
函數return
之前延時1秒,這只是一種權宜之計,即使主綫程等待1秒,內核也不一定會調度新創建的綫程執行,下一節我們會看到更好的辦法。
思考題:主綫程在一個全局變數ntid
中保存了新創建的綫程的id,如果新創建的綫程不調用pthread_self
而是直接打印這個ntid
,能不能達到同樣的效果?
如果需要只終止某個綫程而不終止整個進程,可以有三種方法:
從綫程函數return
。這種方法對主綫程不適用,從main
函數return
相當於調用exit
。
一個綫程可以調用pthread_cancel
終止同一進程中的另一個綫程。
綫程可以調用pthread_exit
終止自己。
用pthread_cancel
終止一個綫程分同步和非同步兩種情況,比較複雜,本章不打算詳細介紹,讀者可以參考[APUE2e]。下面介紹pthread_exit
的和pthread_join
的用法。
#include <pthread.h> void pthread_exit(void *value_ptr);
value_ptr
是void *
類型,和綫程函數返回值的用法一樣,其它綫程可以調用pthread_join
獲得這個指針。
需要注意,pthread_exit
或者return
返回的指針所指向的內存單元必須是全局的或者是用malloc
分配的,不能在綫程函數的棧上分配,因為當其它綫程得到這個返回指針時綫程函數已經退出了。
#include <pthread.h> int pthread_join(pthread_t thread, void **value_ptr);
返回值:成功返回0,失敗返回錯誤號
調用該函數的綫程將掛起等待,直到id為thread
的綫程終止。thread
綫程以不同的方法終止,通過pthread_join
得到的終止狀態是不同的,總結如下:
如果thread
綫程通過return
返回,value_ptr
所指向的單元裡存放的是thread
綫程函數的返回值。
如果thread
綫程被別的綫程調用pthread_cancel
異常終止掉,value_ptr
所指向的單元裡存放的是常數PTHREAD_CANCELED
。
如果thread
綫程是自己調用pthread_exit
終止的,value_ptr
所指向的單元存放的是傳給pthread_exit
的參數。
如果對thread
綫程的終止狀態不感興趣,可以傳NULL
給value_ptr
參數。
看下面的例子(省略了出錯處理):
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> void *thr_fn1(void *arg) { printf("thread 1 returning\n"); return (void *)1; } void *thr_fn2(void *arg) { printf("thread 2 exiting\n"); pthread_exit((void *)2); } void *thr_fn3(void *arg) { while(1) { printf("thread 3 writing\n"); sleep(1); } } int main(void) { pthread_t tid; void *tret; pthread_create(&tid, NULL, thr_fn1, NULL); pthread_join(tid, &tret); printf("thread 1 exit code %d\n", (int)tret); pthread_create(&tid, NULL, thr_fn2, NULL); pthread_join(tid, &tret); printf("thread 2 exit code %d\n", (int)tret); pthread_create(&tid, NULL, thr_fn3, NULL); sleep(3); pthread_cancel(tid); pthread_join(tid, &tret); printf("thread 3 exit code %d\n", (int)tret); return 0; }
運行結果是:
$ ./a.out thread 1 returning thread 1 exit code 1 thread 2 exiting thread 2 exit code 2 thread 3 writing thread 3 writing thread 3 writing thread 3 exit code -1
可見在Linux的pthread庫中常數PTHREAD_CANCELED
的值是-1。可以在標頭檔pthread.h
中找到它的定義:
#define PTHREAD_CANCELED ((void *) -1)
一般情況下,綫程終止後,其終止狀態一直保留到其它綫程調用pthread_join
獲取它的狀態為止。但是綫程也可以被置為detach狀態,這樣的綫程一旦終止就立刻回收它占用的所有資源,而不保留終止狀態。不能對一個已經處于detach狀態的綫程調用pthread_join
,這樣的調用將返回EINVAL
。對一個尚未detach的綫程調用pthread_join
或pthread_detach
都可以把該綫程置為detach狀態,也就是說,不能對同一綫程調用兩次pthread_join
,或者如果已經對一個綫程調用了pthread_detach
就不能再調用pthread_join
了。
#include <pthread.h> int pthread_detach(pthread_t tid);
返回值:成功返回0,失敗返回錯誤號。