這篇文章主要介紹了linux多線程編程詳解教程,提供線程通過信號量實現通信的代碼,大家參考使用吧
線程分類 線程按照其調度者可以分為用戶級線程和核心級線程兩種。 (1)用戶級線程 用戶級線程主要解決的是上下文切換的問題,它的調度算法和調度過程全部由用戶自行選擇決定,在運行時不需要特定的內核支持。在這裡,操作系統往往會提供一個用戶空間的線程庫,該線程庫提供了線程的創建、調度、撤銷等功能,而內核仍然僅對進程進行管理。如果一個進程中的某一個線程調用了一個阻塞的系統調用,那麼該進程包括該進程中的其他所有線程也同時被阻塞。這種用戶級線程的主要缺點是在一個進程中的多個線程的調度中無法發揮多處理器的優勢。 (2)核心級線程 這種線程允許不同進程中的線程按照同一相對優先調度方法進行調度,這樣就可以發揮多處理器的並發優勢。 現在大多數系統都采用用戶級線程與核心級線程並存的方法。一個用戶級線程可以對應一個或幾個核心級線程,也就是“一對一”或“多對一”模型。這樣既可滿足多處理機系統的需要,也可以最大限度地減少調度開銷。 Linux的線程實現是在核外進行的,核內提供的是創建進程的接口do_fork()。內核提供了兩個系統調用clone()和fork(),最終都用不同的參數調用do_fork()核內API。當然,要想實現線程,沒有核心對多進程(其實是輕量級進程)共享數據段的支持是不行的,因此,do_fork()提供了很多參數,包括CLONE_VM(共享內存空間)、CLONE_FS(共享文件系統信息)、 CLONE_FILES(共享文件描述符表)、CLONE_SIGHAND(共享信號句柄表)和CLONE_PID(共享進程ID,僅對核內進程,即0號進程有效)。當使用fork系統調用時,內核調用do_fork()不使用任何共享屬性,進程擁有獨立的運行環境,而使用 pthread_create()來創建線程時,則最終設置了所有這些屬性來調用__clone(),而這些參數又全部傳給核內的do_fork(),從而創建的“進程”擁有共享的運行環境,只有棧是獨立的,由__clone()傳入。 Linux線程在核內是以輕量級進程的形式存在的,擁有獨立的進程表項,而所有的創建、同步、刪除等操作都在核外pthread庫中進行。pthread 庫使用一個管理線程(__pthread_manager(),每個進程獨立且唯一)來管理線程的創建和終止,為線程分配線程ID,發送線程相關的信號(比如Cancel),而主線程(pthread_create())的調用者則通過管道將請求信息傳給管理線程。 主要函數說明 1.線程的創建和退出 pthread_create 線程創建函數 int pthread_create (pthread_t * thread_id,__const pthread_attr_t * __attr,void *(*__start_routine) (void *),void *__restrict __arg); 線程創建函數第一個參數為指向線程標識符的指針,第二個參數用來設置線程屬性,第三個參數是線程運行函數的起始地址,最後一個參數是運行函數的參數。這裡,我們的函數thread 不需要參數,所以最後一個參數設為空指針。第二個參數我們也設為空指針,這樣將生成默認屬性的線程。當創建線程成功時,函數返回0,若不為0 則說明創建線程失敗,常見的錯誤返回代碼為EAGAIN 和EINVAL。前者表示系統限制創建新的線程,例如線程數目過多了;後者表示第二個參數代表的線程屬性值非法。創建線程成功後,新創建的線程則運行參數三和參數四確定的函數,原來的線程則繼續運行下一行代碼。 pthread_join 函數,來等待一個線程的結束。 函數原型為:int pthread_join (pthread_t __th, void **__thread_return) 第一個參數為被等待的線程標識符,第二個參數為一個用戶定義的指針,它可以用來存儲被等待線程的返回值。這個函數是一個線程阻塞的函數,調用它的函數將一直等待到被等待的線程結束為止,當函數返回時,被等待線程的資源被收回。線程只能被一個線程等待終止,並且應處於joinable狀態(非detached)。 pthread_exit 函數 一個線程的結束有兩種途徑,一種是線程運行的函數結束了,調用它的線程也就結束了; 另一種方式是通過函數pthread_exit 來實現。它的函數原型為:void pthread_exit (void *__retval)唯一的參數是函數的返回代碼,只要pthread_join 中的第二個參數thread_return 不是NULL,這個值將被傳遞給thread_return。最後要說明的是,一個線程不能被多個線程等待,否則第一個接收到信號的線程成功返回,其余調用pthread_join 的線程則返回錯誤代碼ESRCH。 2.線程屬性 pthread_create函數的第二個參數線程的屬性。將該值設為NULL,也就是采用默認屬性,線程的多項屬性都是可以更改的。這些屬性主要包括綁定屬性、分離屬性、堆棧地址、堆棧大小、優先級。其中系統默認的屬性為非綁定、非分離、缺省1M 的堆棧、與父進程同樣級別的優先級。下面首先對綁定屬性和分離屬性的基本概念進行講解。 綁定屬性:Linux中采用“一對一”的線程機制,也就是一個用戶線程對應一個內核線程。綁定屬性就是指一個用戶線程固定地分配給一個內核線程,因為CPU時間片的調度是面向內核線程 (也就是輕量級進程)的,因此具有綁定屬性的線程可以保證在需要的時候總有一個內核線程與之對應。而與之相對的非綁定屬性就是指用戶線程和內核線程的關系不是始終固定的,而是由系統來控制分配的。 分離屬性:分離屬性是用來決定一個線程以什麼樣的方式來終止自己。在非分離情況下,當一個線程結束時,它所占用的系統資源並沒有被釋放,也就是沒有真正的終止。只有當pthread_join()函數返回時,創建的線程才能釋放自己占用的系統資源。而在分離屬性情況下,一個線程結束時立即釋放它所占有的系統資源。 這裡要注意的一點是,如果設置一個線程的分離屬性,而這個線程運行又非常快,那麼它很可能在pthread_create 函數返回之前就終止了,它終止以後就可能將線程號和系統資源移交給其他的線程使用,這時調用pthread_create 的線程就得到了錯誤的線程號。 設置綁定屬性: int pthread_attr_init(pthread_attr_t *attr) int pthread_attr_setscope(pthread_attr_t *attr, int scope) int pthread_attr_getscope(pthread_attr_t *tattr, int *scope) scope:PTHREAD_SCOPE_SYSTEM:綁定,此線程與系統中所有的線程競爭 PTHREAD_SCOPE_PROCESS:非綁定,此線程與進程中的其他線程競爭 設置分離屬性: int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate) int pthread_attr_getdetachstate(const pthread_attr_t *tattr,int *detachstate) detachstate PTHREAD_CREATE_DETACHED:分離 PTHREAD _CREATE_JOINABLE:非分離 設置調度策略: int pthread_attr_setschedpolicy(pthread_attr_t * tattr, int policy) int pthread_attr_getschedpolicy(pthread_attr_t * tattr, int *policy) policy SCHED_FIFO:先入先出 SCHED_RR:循環 SCHED_OTHER:實現定義的方法 設置優先級: int pthread_attr_setschedparam (pthread_attr_t *attr, struct sched_param *param) int pthread_attr_getschedparam (pthread_attr_t *attr, struct sched_param *param) 3.線程訪問控制 1)互斥鎖(mutex) 通過鎖機制實現線程間的同步。同一時刻只允許一個線程執行一個關鍵部分的代碼。 1 int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutex_attr_t *mutexattr); 2 int pthread_mutex_lock(pthread_mutex_t *mutex); 3 int pthread_mutex_unlock(pthread_mutex_t *mutex); 4 int pthread_mutex_destroy(pthread_mutex_t *mutex); (1)先初始化鎖init()或靜態賦值pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIER (2)加鎖,lock,trylock,lock阻塞等待鎖,trylock立即返回EBUSY (3)解鎖,unlock需滿足是加鎖狀態,且由加鎖線程解鎖 (4)清除鎖,destroy(此時鎖必需unlock,否則返回EBUSY) mutex 分為遞歸(recursive) 和非遞歸(non-recursive)兩種,這是POSIX 的叫法,另外的名字是可重入(Reentrant) 與非可重入。這兩種mutex 作為線程間(inter-thread) 的同步工具時沒有區別,它們的惟一區別在於:同一個線程可以重復對recursive mutex 加鎖,但是不能重復對non-recursive mutex 加鎖。 首選非遞歸mutex,絕對不是為了性能,而是為了體現設計意圖。non-recursive 和recursive 的性能差別其實不大,因為少用一個計數器,前者略快一點點而已。在同一個線程裡多次對non-recursive mutex 加鎖會立刻導致死鎖,我認為這是它的優點,能幫助我們思考代碼對鎖的期求,並且及早(在編碼階段)發現問題。毫無疑問recursive mutex 使用起來要方便一些,因為不用考慮一個線程會自己把自己給鎖死了,我猜這也是Java 和Windows 默認提供recursive mutex 的原因。(Java 語言自帶的intrinsic lock 是可重入的,它的concurrent 庫裡提供ReentrantLock,Windows的CRITICAL_SECTION 也是可重入的。似乎它們都不提供輕量級的non-recursive mutex。) 2)條件變量(cond) 利用線程間共享的全局變量進行同步的一種機制。 1 int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr); 2 int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex); 3 int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex,const timespec *abstime); 4 int pthread_cond_destroy(pthread_cond_t *cond); 5 int pthread_cond_signal(pthread_cond_t *cond); 6 int pthread_cond_broadcast(pthread_cond_t *cond); //解除所有線程的阻塞 (1)初始化. init()或者pt