一、Linux環境下的線程
相對於其他操作系統,Linux系統內核只提供了輕量級進程的支持,並未實現線程模型。Linux是一種“多進程單線程”的操作系統,Linux本身只有進程的概念,而其所謂的“線程”本質上在內核里仍然是進程。
進程是資源分配的單位,同一進程中的多個線程共享該進程的資源(如作為共享內存的全局變量)。Linux中所謂的“線程”只是在被創建時clone了父進程的資源,因此clone出來的進程表現為“線程”,這一點一定要弄清楚。因此,Linux“線程”這個概念只有在打引號的情況下才是最准確的。
目前Linux中最流行的線程機制為LinuxThreads,所采用的就是線程-進程“一對一”模型,調度交給核心,而在用戶級實現一個包括信號處理在內的線程管理機制。LinuxThreads由Xavier Leroy負責開發完成,並已綁定在GLIBC中發行,它實現了一種BiCapitalized面向Linux的Posix 1003.1c “pthread”標准接口。Linuxthread可以支持Intel、Alpha、MIPS等平台上的多處理器系統。
需要注意的是,Linuxthread線程模型存在一些缺陷,尤其是在信號處理、調度和進程間同步原語方面都存在問題。並且,這個線程模型也不符合POSIX標准的要求。為了解決LinuxThread的缺陷,RedHat開發了一套符合POSIX標准的新型線程模型:NPTL(Native POSIX Thread Library)。關於Linuxthread與NPTL的比較,請參考文章:Linux 線程模型的比較:LinuxThreads 和 NPTL。
二、Linux環境下的多線程編譯支持
按照POSIX 1003.1c 標准編寫的程序與Linuxthread 庫相鏈接即可支持Linux平台上的多線程,在程序中需包含頭文件pthread. h,在編譯鏈接時使用命令:
gcc -D -REENTRANT -lpthread xxx. c
其中-REENTRANT宏使得相關庫函數(如stdio.h、errno.h中函數) 是可重入的、線程安全的(thread-safe),-lpthread則意味着鏈接庫目錄下的libpthread.a或libpthread.so文件。
在一個多線程程序里,默認情況下,只有一個errno變量供所有的線程共享。在一個線程准備獲取剛才的錯誤代碼時,該變量很容易被另一個線程中的函數調用所改變。類似的問題還存在於fputs之類的函數中,這些函數通常用一個單獨的全局性區域來緩存輸出數據。
為解決這個問題,需要使用可重入的例程。可重入代碼可以被多次調用而仍然工作正常。編寫的多線程程序,通過定義宏_REENTRANT來告訴編譯器我們需要可重入功能,這個宏的定義必須出現於程序中的任何#include語句之前。
_REENTRANT為我們做三件事情,並且做的非常優雅:
(1)它會對部分函數重新定義它們的可安全重入的版本,這些函數名字一般不會發生改變,只是會在函數名后面添加_r字符串,如函數名gethostbyname變成gethostbyname_r。
(2)stdio.h中原來以宏的形式實現的一些函數將變成可安全重入函數。
(3)在error.h中定義的變量error現在將成為一個函數調用,它能夠以一種安全的多線程方式來獲取真正的errno的值。
三、Linux環境下的多線程函數
3.1 線程創建
在進程被創建時,系統會為其創建一個主線程,而要在進程中創建新的線程,則可以調用pthread_create函數:
#include <pthread.h> int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
參數說明:
- thread:指向pthread_create類型的指針,用於引用新創建的線程。
- attr:用於設置線程的屬性,一般不需要特殊的屬性,所以可以簡單地設置為NULL。
- start_routine:傳遞新線程所要執行的函數地址。
- arg:新線程所要執行的函數的參數。
返回值:
調用如果成功,則返回值是0;如果失敗則返回錯誤代碼。
每個線程都有自己的線程ID,以便在進程內區分。線程ID在pthread_create調用時回返給創建線程的調用者;一個線程也可以在創建后使用pthread_self()調用獲取自己的線程ID:
pthread_self (void);
3.2 線程退出
線程的退出方式有三種:
(1)執行完成后隱式退出;
(2)由線程本身顯示調用pthread_exit 函數退出;
pthread_exit (void * retval);
(3)被其他線程用pthread_cance函數終止:
pthread_cancel (pthread_t thread);
如果一個線程要等待另一個線程的終止,可以使用pthread_join函數,該函數的作用是調用pthread_join的線程將被掛起直到線程ID為參數thread的線程終止:
pthread_join (pthread_t thread, void** threadreturn);
3.3 簡單的多線程示例
一個簡單的Linux多線程示例如下:

#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> void *thread_function(void *arg); char message[] = "Hello World"; int main() { int res; pthread_t a_thread; void *thread_result; 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"); res = pthread_join(a_thread, &thread_result); if (res != 0) { perror("Thread join failed!/n"); exit(EXIT_FAILURE); } printf("Thread joined, it returned %s/n", (char *)thread_result); printf("Message is now %s/n", message); exit(EXIT_FAILURE); } void *thread_function(void *arg) { printf("thread_function is running. Argument was %s/n", (char *)arg); sleep(3); strcpy(message, "Bye!"); pthread_exit("Thank you for your CPU time!"); }
編譯語句如下:
gcc -D_REENTRANT thread1.c -o thread1 –lpthread
輸出結果是:
$./thread1[輸出]: thread_function is running. Argument was Hello World Waiting for thread to finish... Thread joined, it returned Thank you for your CPU time! Message is now Bye!
在這個例子中,pthread_exit(void *retval)本身返回的就是指向某個對象的指針,因此,pthread_join(pthread_t th, void **thread_return);中的thread_return是二級指針,指向線程返回值的指針。可以看到,我們創建的新線程修改的數組message的值,而原先的線程也可以訪問該數組。如果我們調用的是fork而不是pthread_create,就不會有這樣的效果了。因為fork創建子進程之后,子進程會拷貝父進程,兩者分離,相互不干擾,而線程之間則是共享進程的相關資源。
小結:
本文主要講了Linux環境下的多線程基本概念,包括多線程的實現方式、函數接口、功能特性等。