目錄
11、POSIX多線程技術
1、線程
什麼是線程
- 在一個程序裏的多個執行路線就叫做線程(thread)。更準確的定義是:線程是“一個進程內部的控制序列”
- 一切進程至少都有一個執行線程
fork和創建新線程的區別
- 當一個進程執行一個fork調用的時候,會創建出進程的一個新拷貝,新進程將擁有它自己的變量和它自己的PID。這個新進程的運行時間是獨立的,它在執行時幾乎完全獨立於創建它的進程
- 在進程裏面創建一個新線程的時候,新的執行線程會擁有自己的堆棧(因此也就有自己的局部變量),但要與它的創建者共享全局變量、文件描述符、信號處理器和當前的子目錄狀態
線程的優點
- 創建一個新線程的代價要比創建一個新進程小得多
- 與進程之間的切換相比,線程之間的切換需要操作系統做的工作至少在理論上要少很多
- 線程佔用的資源要比進程少很多
線程的缺點
- 編寫多線程需要更全面更深入的考慮,在一個多線程程序裏,因時間分配上的細微偏差或者因共享了不該共享的變量而造成不良影響的可能性是很大的
- 調試一個多線程程序也比調試一個單線程程序困難得多
線程函數庫
- 與線程有關的函數構成了一個完整的系列,絕大多數函數的名字都是以“pthread_”打頭的
- 要使用這些函數庫,要通過引入頭文件<pthread.h>
- 鏈接這些線程函數庫時要使用編譯器命令的“-lpthread”選項
線程標識符
- 像每個進程有一個進程ID一樣,每個線程也有一個線程ID
- 進程ID在整個系統中是唯一的,但線程不同,線程ID只在它所屬的進程環境中有效
- 線程ID用pthread_t數據類型來表示,實現的時候可以用一個結構來代表pthread_t數據類型,所以可以移植的操作系統不能把它作爲整數處理
2、pthread_create函數
- 作用:創建一個新的線程
int pthread_create(pthread_t *thread,
pthread_attr_t *attr,
void*(*start_routine)(void*),
void *arg);
- 參數
- thread:新線程創建成功後,保存新線程的標識符
- attr:設置線程的屬性,一般不需要什麼特殊的屬性,直接傳 NULL即可
- start_routine: 是個函數地址,線程啓動後要執行的函數
- arg:傳給線程啓動函數的參數
"void* (start_routine)(void) "表示需要我們傳遞的一個函數地址,該函數以一個指向void的指針爲參數,返回的也是一個指向void的指針。
調用成功時返回值是“0”,如果失敗則返回一個錯誤。
3、pthread_exit函數
- 作用:結束調用了這個函數的線程
void pthread_exit (void *retbal);
- 返回一個指向某個對象的指針。
- 絕不要用它返回一個指向一個局部變量的指針
- 線程在結束時必須調用pthread_exit函數,這與一個進程在結束時要調用exit函數是同樣的道理
4、pthread_join函數
- 作用:在線程結束後把它們歸併到一起
int pthread_join(pthread_t th, void **thread_return);
- th: 指定了將要等待的線程標識符
- thread_return: 它指向另外一個指針,而後者指向線程的返回值
- 成功時返回“0”,失敗時返回一個錯誤代碼
- pthread_join相當於進程用來等待子進程的wait函數
創建一個線程默認的狀態是joinable,如果一個可結合線程結束運行但沒有被join,則它的狀態類似於進程中的Zombie Process,即還有一部分資源沒有被回收,所以創建線程者應該調用pthread_join來等待線程運行結束,並可得到線程的退出代碼,回收其資源。
5、一個簡單的線程化程序
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
char message[] = "Hello World";
void *thread_function(void *arg){
printf("thread_function is running.Argument was %s\n",(char*)arg);
sleep(3);
strcpy(message,"Bye");
pthread_exit((void *)"thank you");
}
int main() {
int res;
pthread_t a_thread;
res = pthread_create(&a_thread,NULL,thread_function,(void*)message);
if(res != 0) {
perror("Thread creation failed");
exit(EXIT_FAILURE);
}
printf("Waiting for thread to finish..\n");
void *thread_result;
res = pthread_join(a_thread,&thread_result);
if(res != 0) {
perror("Thread join failed");
exit(EXIT_FAILURE);
}
printf("Thread joined, it returned %s\n",(char*)thread_result);
printf("Message is now %s\n",message);
exit(EXIT_SUCCESS);
return 0;
}
6、pthread_detach回收資源
-
在任何一個時間點上,線程是可結合的(joinable)或者是分離的(detached)。一個可結合的線程能夠被其他線程收回其資源和殺死。在被其他線程回收之前,它的存儲器資源(例如棧)是不釋放的。相反,一個分離的線程是不能被其他線程回收或殺死的,它的存儲器資源在它終止時由系統自動釋放。
-
默認情況下,線程被創建成可結合的。爲了避免存儲器泄漏,每個可結合線程都應該要麼被顯示地回收,即調用pthread_join;要麼通過調用pthread_detach函數被分離。
-
由於調用pthread_join後,如果該線程沒有運行結束,調用者會被阻塞,在有些情況下我們並不希望如此。
-
這時可以在子線程中加入代碼 pthread_detach(pthread_self())
-
或者父線程調用pthread_detach(thread_id)(非阻塞,可立即返回)
-
這將該子線程的狀態設置爲分離的(detached),如此一來,該線程運行結束後會自動釋放所有資源。
pthread_detach示例
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void *print_message(void *ptr)
{
pthread_detach(pthread_self());//將狀態改爲unjoinable狀態,確保資源的釋放
static int g;
printf("%d\n",g++);
pthread_exit(0);//pthread_exit時自動會被釋放
}
int main(int argc,char *argv[])
{
pthread_t thread_id;
while(1){// 一個線程默認的狀態是joinable
pthread_create(&thread_id,NULL,print_message,NULL);
}
return 0;
}