Linux 之二 Linux 多线程
??最近在整理舊電腦時,發現了一些剛入行時的學習記錄,以及最早使用新浪博客 http://blog.sina.com.cn/zcshou 寫的一些文章。最近要重拾 Linux,所以把這些 Word 文檔重新排版轉到此博客上,一來復習一下,二來在 CSDN 上作個存檔!
至于新浪博客上的文章就留那里吧!話說新浪博客是不是塊倒閉了?還有一點,如果沒記錯,文章里使用的是 CentOS。
??注意,這些文章里的內容多數可能來自網絡(當初學習時翻看了各種網絡資料)但應該不是原版抄襲(作為一名工科生,不動手實踐怎么能行!)。如果您發現其中內容有侵權,請私信我,我將立刻處理!
進程與線程
??在早期的操作系統中并沒有線程的概念,進程是擁有資源和獨立運行的最小單位,也是程序執行的最小單位。后來,隨著計算機的發展,由于進程之間的切換開銷較大,已經無法滿足越來越復雜的程序的要求了,于是就發明了線程。
??線程是程序執行中一個單一的順序控制流程,是程序執行流的最小單元,是處理器調度和分派的基本單位。一個進程可以有一個或多個線程,各個線程之間共享程序的資源(Text,heap 和 global data 等)。
號等),某進程內的線程在其他進程不可見;
??要注意的是,對于多線程來說,由于同一個進程空間中存在多個棧,任何一個空白區域被填滿都會導致 stack overflow 的問題。
并行與并發
??并發(Concurrency)是指同一時刻只能有一個任務得到執行,但多個任務被快速輪換執行,使得在宏觀上有多個進程被同時執行的效果(宏觀上并行)。其中,并發又有偽并發和真并發,偽并發是指單核處理器的并發,真并發是指多核處理器的并發。
??并行(Parallelism)是指同一時刻有多個任務同時在執行。只有在多 CPU 的情況下,才能實現并行。
詳細的可以參考 http://tutorials.jenkov.com/java-concurrency/concurrency-vs-parallelism.html
Linux 線程
??在早期的類 Unix 系統中是沒有線程概念, Linux 內核也只提供了 輕量級進程(light-weight process) 的支持,未實現線程模型。Linux 是一種 “多進程單線程” 的操作系統。Linux 本身只有進程的概念,而其所謂的 “線程” 本質上在內核里仍然是進程。
??當 Linux 最初開發時,在內核中并不能真正支持線程。但是它的確可以通過 clone() 系統調用,將進程作為可調度的實體。這個調用創建了調用進程(calling process)的一個拷貝,這個拷貝與調用進程共享相同的地址空間。LinuxThreads 項目使用這個調用來實現在用戶空間模擬對線程的支持。
??不幸的是,這種方法有一些缺點,尤其是在信號處理、調度和進程間同步原語方面都存在問題。另外,這個線程模型也不符合 POSIX 的要求。在源碼看來,就是 Linux 內核的 clone() 沒有實現對 CLONE_PID 參數的支持。
關于 LinuxThreads
??最初,Linux 中最流行的線程機制為 LinuxThreads,所采用的就是 進程與線程一對一模型,調度交給核心,而在用戶級實現一個包括信號處理在內的線程管理機制。LinuxThreads 由 Xavier Leroy (Xavier.Leroy@inria.fr) 負責開發完成,并已綁定在 GLIBC 中發行。
??通過系統調用 clone() 創建子進程時,可以有選擇性地讓子進程共享父進程所引用的資源,這樣的子進程通常稱為輕量級進程。linux上的線程就是基于輕量級進程,由用戶態的 pthread 庫 實現。使用 pthread 以后, 在用戶看來, 每一個 task_struct 就對應一個線程,而一組線程以及它們所共同引用的一組資源就是一個進程。
??但是, 一組線程并不僅僅是引用同一組資源就夠了, 它們還必須被視為一個整體。對此,POSIX(Portable Operation System Interface)標準提出了如下要求:
??在 linux 2.6 以前,pthread 線程庫對應的實現使用的便是一個名叫 LinuxThreads 的 lib,linuxThreads 利用前面提到的輕量級進程來實現線程,但是對于 POSIX 提出的那些要求,linuxThreads 除了上面的第 5 點以外,都沒有實現(實際上是無能為力):
??LinuxThreads 的問題,特別是兼容性上的問題,嚴重阻礙了 Linux 上的跨平臺應用(如 Apache)采用多線程設計,從而使得 Linux 上的線程應用一直保持在比較低的水平。在 Linux 社區中,已經有很多人在為改進線程性能而努力,其中既包括用戶級線程庫,也包括核心級和用戶級配合改進的線程庫。
??最為人看好的有兩個項目,一個是 RedHat 公司牽頭研發的 NPTL(Native Posix Thread Library),另一個則是 IBM 投資開發的 NGPT(Next Generation Posix Threading),二者都是圍繞完全兼容 POSIX 1003.1c,同時在核內和核外做工作以而實現多對多線程模型。這兩種模型都在一定程度上彌補了 LinuxThreads 的缺點,且都是重起爐灶全新設計的。
關于 NPTL
??NPTL 全稱為 Native POSIX Thread Library,是 Linux 線程的一個新實現,它克服了 LinuxThreads 的缺點,同時也符合 POSIX 的需求。與 LinuxThreads 相比,它在性能和穩定性方面都提供了重大的改進。
??在技術實現上,NPTL 仍然采用 進程與線程一對一模型,并配合 glibc 和最新的 Linux Kernel 2.5.x 在信號處理、線程同步、存儲管理等多方面進行了優化。和 LinuxThreads 不同,NPTL 沒有使用管理線程,核心線程的管理直接放在核內進行,這也帶了性能的優化。主要是因為核心的問題,NPTL 仍然不是 100% POSIX 兼容的,但就性能而言相對 LinuxThreads 已經有很大程度上的改進了。
??到了 Linux 2.6,glibc 中有了一種新的 pthread 線程庫 – NPTL(Native POSIX Threading Library)。NPTL 實現了前面提到的 POSIX 的全部 5 點要求。但是,實際上,與其說是 NPTL 實現了,不如說是 Linux 內核實現了。在 Linux 2.6 中,內核有了線程組的概念, task_struct 結構中增加了一個 tgid(thread group id)字段。如果這個 task 是一個"主線程",則它的 tgid 等于pid,否則 tgid 不等于進程的 pid(即主線程的 pid)。在 clone 系統調用中,傳遞 CLONE_THREAD 參數就可以把新進程的 tgid 設置為父進程的 tgid(否則新進程的tgid會設為其自身的 pid)。
??有了 tgid,內核或相關的 shell 程序就知道某個 tast_struct 是代表一個進程還是代表一個線程,也就知道在什么時候該展現它們,什么時候不該展現(比如在 ps 的時候,線程就不要展現了)。而 getpid(獲取進程 ID)系統調用返回的也是 tast_struct 中的 tgid,而tast_struct 中的 pid 則由 gettid 系統調用來返回。
??通常可以通過如下方法驗證自己使用的系統是否支持 NPTL,存在類似以下 NPTL 行或 Native POSIX Threads Library by Ulrich Drepper et al 行表示支持:
關于 NGPT
??IBM 的開放源碼項目 NGPT 在 2003 年 1 月 10 日推出了穩定的 2.2.0 版,但相關的文檔工作還差很多。就目前所知,NGPT 是基于 GNU Pth(GNU Portable Threads)項目而實現的 M:N 模型,而 GNU Pth 是一個經典的用戶級線程庫實現。
??按照 2003 年 3 月 NGPT 官方網站上的通知,NGPT 考慮到 NPTL 日益廣泛地為人所接受,為避免不同的線程庫版本引起的混亂,今后將不再進行進一步開發,而今進行支持性的維護工作。也就是說,NGPT 已經放棄與 NPTL 競爭下一代 Linux POSIX 線程庫標準。
其他高效線程機制
??此處不能不提到 Scheduler Activations。這個 1991 年在 ACM 上發表的多線程內核結構影響了很多多線程內核的設計,其中包括 Mach3.0、NetBSD 和商業版本 Digital Unix(現在叫 Compaq True64 Unix)。它的實質是在使用用戶級線程調度的同時,盡可能地減少用戶級對核心的系統調用請求,而后者往往是運行開銷的重要來源。采用這種結構的線程機制,實際上是結合了用戶級線程的靈活高效和核心級線程的實用性,因此,包括 Linux、FreeBSD 在內的多個開放源碼操作系統設計社區都在進行相關研究,力圖在本系統中實現Scheduler Activations。
多線程編程
??Linux 系統下的多線程遵循 POSIX 線程接口,稱為 pthread。編寫 Linux 下的多線程程序,需要使用頭文件 pthread.h,連接時需要使用庫 libpthread.a。因此,后面的編譯必須在選項中加入 -lpthread 選項,否則提示找不到 pthread_create() 等這些函數。
??按照 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 文件。使用 Linuxthread 庫需要 2.0 以上版本的 Linux 內核及相應版本的 C 庫(libc 5.2.18、libc 5.4.12、libc 6)。
“線程”控制
設置線程屬性
線程屬性類型為: pthread_attr_t
-
scope:用來設置創建線程的爭用范圍
- PTHREAD_SCOPE_SYSTEM:使用此值創建的線程將于系統中的其他線程爭用資源
- PTHREAD_SCOPE_PROCESS:默認值,使用此值創建的線程將于進程內的其他線程爭用資源。
相關接口定義:
- int pthread_attr_setscope(pthread_attr_t *attr, int scope);
- int pthread_attr_getscope(const pthread_attr_t *attr, int *scope);
-
detachstate:用于設置線程處于分離狀態還是可連接狀態
- PTHREAD_CREATE_DETACHED:線程處于分離狀態,終止時,系統自動回收資源,對該線程調用pthread_detach或pthread_join將失敗
- PTHREAD_CREATE_JOINABLE:默認值。線程處于可連接狀態,終止時,系統不會自動回收資源。需要調用 pthread_detach 或 pthread_join 回收資源。如果用了 PTHREAD_CREATE_JOINABLE,在所有線程退出前,進程不會退出。
相關接口定義:
- int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
- int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
-
stackaddr:默認值為 NULL。新線程由系統分配棧地址
-
stacksize:默認為 0。新線程具有系統定義的棧大小
-
priority:默認為 0。新線程的優先級
-
inheritsched:用于設置線程的繼承屬性
- PTHREAD_INHERIT_SCHED:從父線程繼承調度策略和屬性
- PTHREAD_EXPLICIT_SCHED:默認值。從屬性對象獲得調度策略和屬性,新線程不繼承父線程的調度優先級
相關接口定義:
- int pthread_attr_setinheritsched(pthread_attr_t *attr, int inherit);
- int pthread_attr_getinheritsched(const pthread_attr_t *attr, int *inherit);
-
schedpolicy:設置線程的調度策略,要使用此屬性,inheritsched 必須為 PTHREAD_EXPLICIT_SCHED
- SCHED_OTHER:系統默認策略。新線程將一直運行,直到被搶占或者直到線程阻塞或停止為止
- SCHED_FIFO:先進先出策略。具有最高優先級,等待時間最長的線程將別首先執行
- SCHED_RR:輪轉調度,類似 FIFO,但加了時間輪片算法
相關接口定義:
- int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
- int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy);
線程創建
??進程被創建時,系統會為其創建一個主線程,而要在進程中創建新的線程,則可以調用 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
- 返回值: 若是成功建立線程返回 0,否則返回錯誤的編號
- 形式參數:
- pthread_t *thread:如果成功指向新線程標識 ID,失敗時,其值未知
- const pthread_attr_t *attr:創建線程時的線程屬性。如果設置為 NULL,將使用系統默認屬性。要么為 NULL,要么在調用 pthread_create 前用 pthread_attr_init 初始化
- void *(*start_routine) (void *):新線程函數的入口指針
- void *arg:void *(*start_routine) (void *) 的參數。
??每個線程都有自己的線程 ID,以便在進程內區分。線程 ID 在 pthread_create 調用時回返給創建線程的調用者(由第一個參數保存);一個線程也可以在創建后使用 pthread_t pthread_self (void) ; 調用獲取自己的線程ID。
等待線程結束
等待線程結束需要使用接口:int pthread_join(pthread_t th, void **thread_return);
- 返回值: 如果成功返回 0,反之非 0
- 參數:
- th:被等待的線程標識符,即線程創建時返回的線程 ID。
- thread_return:是一個用戶定義的指針,指向一個保存等待線程的完整退出狀態的區域,用來存儲被等待線程的返回值。 如果為 NULL,則線程的退出狀態信息丟失。
- 注意:
- 創建一個線程默認的狀態是 joinable,如果一個線程結束運行但沒有被 join,則它的狀態類似于進程中的 Zombie Process,即還有一部分資源沒有被回收(退出狀態碼),所以創建線程者應該調用pthread_join 來等待線程運行結束,并可得到線程的退出代碼,回收其資源(類似于wait,waitpid)
- 一個線程只能等待連接一個其他線程
- 如果多個線程等待同一個線程,只有一個線程能得到正確的狀態信息
- 有競爭關系的線程間的連接操作將返回一個錯誤
- 如果啟動連接的線程被取消,則處于等待狀態的線程可以被其他線程等待
- 如果目標線程在執行 pthread_join 前結束,則該調用不會引起任何阻塞并立即返回
- 一個未被連接的非獨立線程在線程結束前一直占用資源,直到創建它的進程結束
分離線程
分離線程需要使用接口:int pthread_detach(pthread_t tid);
- 返回值: 如果成功返回 0,反之非 0
- 參數:
- tid:指示要分離的進程 ID。
- 說明:
- pthread_detach 用于分離可結合線程 tid。線程能夠通過以 pthread_self() 為參數的 pthread_detach 調用來分離它們自己。
- pthread_detach 將該子線程的狀態設置為分離的(detached),如此一來,該線程運行結束后會自動釋放所有資源。
取消執行線程
??取消執行線程需要使用接口 int pthread_cancel(pthread_t thread); 。它允許線程以受控方式終止進程中執行的任何線程。目標線程的可取消行狀態和類型決定取消何時生效。
- 返回值: 如果成功返回 0,反之非 0
- 參數:
- thread:要取消的線程標識。
線程退出
線程的退出方式有三種:
實例
#include <stdio.h> #include <pthread.h> #include <unistd.h> pthread_t tidp, tidp2; // 線程ID void *thread1(void *arg) {int *num;num = (int *)arg; // 存放傳遞的參數printf("I‘m thread1, ID is %u\n",pthread_self()); // 打印線程自己的IDprintf("Receive parameter is %d \n",*num); // 打印收到的參數printf("I'm waitting to be cancenl!\n");sleep(20); // 等待線程2 將它結束return (void *)0; // 如果,線程2沒有將其結束,返回0 } void *thread2(void *arg) {int error;printf("I'm thread2. I Will to Cancel thread %u\n",tidp);error = pthread_cancel(tidp); // 取消線程 1if(error<0){printf("Cancel thread %u failed!\n",tidp);return (void *)-1;}else{printf("Cancel thread %u sucessfully! It will not return 0!\n",tidp); // 因為線程2將線程1取消了,所以線程1不能正常返回0}return 0; } int main(int argc, char *argv[]) {int error;void* r_num1,*r_num2;int test = 4;int *attr = &test;error = pthread_create(&tidp,NULL,thread1,(void *)attr); // 創建新線程,并傳遞參數(void *)attrif(error){printf("pthread_create is created is not created ... \n");return -1;}error = pthread_create(&tidp2,NULL,thread2,NULL);if(error){printf("pthread_create is created is not created ... \n");return -1;}error = pthread_join(tidp2,&r_num2); // 阻塞在此處,直到線程2返回error = pthread_join(tidp,&r_num1); // 阻塞在此處,直到線程1返回sleep(1);printf("Thread1 return %d\n",(int)r_num1); // 線程1的返回值,如果線程1被線程2 取消,則返回值不是0printf("Thread2 return %d\n",(int)r_num2); // 線程2的返回值return 0; }線程通信
互斥
??互斥意味著“排它”,即兩個線程不能同時進入被互斥保護的代碼。Linux 下可以通過 pthread_mutex_t 定義互斥體機制完成多線程的互斥操作,該機制的作用是對某個需要互斥的部分,在進入時先得到互斥體,如果沒有得到互斥體,表明互斥部分被其它線程擁有,此時欲獲取互斥體的線程阻塞,直到擁有該互斥體的線程完成互斥部分的操作為止。
??互斥鎖是一個二元變量,其狀態為開鎖和上鎖。在互斥鎖范圍內的任何一個線程都可以對互斥鎖上鎖,但只有鎖住該互斥變量的線程才可以開鎖。但是,互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。
互斥鎖(#include <unistd.h>)
互斥鎖屬性
使用 pthread_mutexattr_t 聲明互斥鎖屬性對象。互斥鎖對象由兩部分組成:
process-shared:share 屬性,它有兩個取值:
- PTHREAD_PROCESS_PRIVATE:默認值
- PTHREAD_PROCESS_SHARED:前者用于同步本進程的不同線程,后者用來不同進程中的線程同步。
相關接口定義:
- int pthread_mutexattr_setshared(pthread_mutexattr_t *atrr, int pshared); 設置互斥鎖屬性
- 參數:
- attr: 要設置的互斥鎖屬性對象指針
- pshared: 要設置的互斥鎖 share 屬性
- 參數:
- int pthread_mutexattr_getshared(pthread_mutexattr_t *atrr, int *pshared); 獲得互斥鎖屬性
- 參數:
- attr: 要獲得的互斥鎖屬性對象指針
- pshared: 用來保存互斥鎖 share 屬性的指針
- 參數:
type:互斥鎖類型。可選的類型有
- PTHREAD_MUTEX_NORMAL
- PTHREAD_MUTEX_ERRORCHECK
- PTHREAD_MUTEX_RECURSIVE
- PTHREAD _MUTEX_DEFAULT(默認)。
它們分別定義了不同的上所、解鎖機制,一般情況下,選用最后一個默認屬性。相關接口定義:
- int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type); 設置互斥鎖類型。
- 參數:
- attr: 要設置的互斥鎖屬性對象指針
- type: 要設置的互斥鎖類型
- 參數:
- int pthread_mutexattr_gettype(pthread_mutexattr_t *atrr, int *type); 獲得互斥鎖類型。
- 參數:
- attr: 要獲得的互斥鎖屬性對象指針
- type: 用來保存互斥鎖類型的指針
- 參數:
互斥鎖屬性初始化
用 int pthread_mutexattr_init(pthread_mutexattr_t *attr); 初始化互斥鎖屬性對象。
- 參數:
- attr:為待初始化的互斥鎖屬性對象的指針。
銷毀互斥鎖屬性對象
用 int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); 銷毀互斥鎖屬性對象
- 參數:
- attr:斥鎖屬性對象的指針
初始化互斥鎖
用 int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); 初始化互斥鎖
- 參數:
- attr:為指向要初始化的互斥鎖的指針
- mutexattr:指向互斥鎖屬性的指針,如果為 NULL,則采用默認屬性
- 注意:
- 用 PTHREAD_MUTEX_INITIALIZER 宏初始化靜態分配的互斥鎖
銷毀互斥鎖
用 int pthread_mutex_destroy(pthread_mutex_t *mutex); 銷毀互斥鎖
- 參數:
- mutex:要銷毀的互斥鎖的指針
上鎖
用 int pthread_mutex_lock(pthread_mutex_t *mutex); 給互斥鎖上鎖
- 參數:
- mutex:要上鎖的互斥鎖指針
- 返回值:如果成功返回0,失敗返回其他
- 注意:
- pthread_mutex_lock 聲明開始用互斥鎖上鎖,此后的代碼直至調用 pthread_mutex_unlock 為止,均被上鎖,即同一時間只能被一個線程調用執行。當一個線程執行到 pthread_mutex_lock 處時,如果該鎖此時被另一個線程使用,那此線程被阻塞,即程序將等待到另一個線程釋放此互斥鎖。
- 用 int pthread_mutex_trylock(pthread_mutex_t *mutex); 非阻塞鎖定。如果一次非阻塞后,如果不能獲取 mutex 引用的互斥對象,調用將立刻返回錯誤
解鎖
??用 int pthread_mutex_unlock(pthread_mutex_t *mutex); 解鎖互斥鎖。當解鎖互斥鎖以后,如果有線程阻塞該互斥鎖,將使用調度策略確定那個線程將獲得互斥鎖。
- 返回值:如果成功返回0,失敗其他
- 參數:
- mutex:要解鎖的互斥鎖指針
- 注意:
- 普通話和缺省方式: 在已經解鎖的互斥鎖上解鎖,將導致未定義行為
- 遞歸和錯誤檢查方式: 在已經解鎖的互斥鎖上解鎖將返回錯誤。對于遞歸方式,解鎖次數必須有鎖定次數一樣多
線程同步
??線程同步是指在互斥的基礎上(大多數情況),通過其他機制實現訪問者對資源的有序訪問。在大多數情況下,同步已經實現了互斥,特別是,所有寫入資源的情況必定時互斥的。
條件變量(#include <unistd.h>)
??互斥鎖一個明顯的缺點是它只有兩種狀態:鎖定和非鎖定。而條件變量通過允許線程阻塞和等待另一個線程發送信號的方法彌補了互斥鎖的不足,它常和互斥鎖一起使用。
??使用時,條件變量被用來阻塞一個線程,當條件不滿足時,線程往往解開相應的互斥鎖并等待條件發生變化。一旦其它的某個線程改變了條件變量,它將通知相應的條件變量喚醒一個或多個正被此條件變量阻塞的線程。這些線程將重新鎖定互斥鎖并重新測試條件是否滿足。一般說來,條件變量被用來進行線程間的同步。
條件變量屬性
??設置條件變量屬性對象。pthread_condattr_t 聲明條件變量屬性對象。條件變量屬性對象由兩部分組成(和互斥鎖一樣):
process-shared:share屬性。它有兩個取值
- PTHREAD_PROCESS_PRIVATE(默認)
- PTHREAD_PROCESS_SHARED。前者用于同步本進程的不同線程,后者用來不同進程中的線程同步。
相關接口定義:
- int pthread_condattr_setshared(pthread_condattr_t *atrr, int pshared); 函數設置條件變量屬性。
- 參數:
- attr: 要設置的條件變量屬性對象指針
- pshared: 要設置的條件變量屬性share屬性
- 參數:
- int pthread_condattr_getshared(pthread_condattr_t *atrr, int *pshared); 獲得條件變量屬性
- 參數:
- attr: 要獲得的條件變量屬性對象指針
- pshared: 用來保存條件變量屬性share屬性的指針
- 參數:
type:條件變量屬性類型。可選的類型有
- PTHREAD_MUTEX_NORMAL
- PTHREAD_MUTEX_ERRORCHECK
- PTHREAD_MUTEX_RECURSIVE
- PTHREAD _MUTEX_DEFAULT(默認)。
它們分別定義了不同的上所、解鎖機制,一般情況下,選用最后一個默認屬性。相關接口定義:
- int pthread_condattr_settype(pthread_ condattr *attr, int type); 函數設置。
- 參數:
- attr: 要設置的件變量屬性對象指針
- type: 要設置的件變量屬性對象類型
- 參數:
- int pthread_condattr_gettype(pthread_condattr_t *atrr, int *type); 獲得件變量屬性對象屬性。
- 參數:
- attr: 要獲得的件變量屬性對象指針
- type: 用來保存件變量屬性對象類型的指針
(
- 參數:
初始化條件變量屬性對象
用 int pthread_condattr_init(pthread_condattr_t *attr); 初始化條件變量屬性對象
- 參數:
- attr:為待初始化的條件變量屬性對象的指針,由上面的函數設置。
銷毀條件變量屬性對象
用 int pthread_condattr_destroy(pthread_condattr_t *attr); 銷毀條件變量屬性對象
- 參數:
- attr:條件變量屬性對象的指針
初始化條件變量
用 int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr); 初始化條件變量
- 參數:
- cond:為指向要初始化的條件變量的指針
- cond_attr:是指向條件變量屬性的指針,如果為NULL,則采用默認屬性
- 注意:
- 用 PTHREAD_MUTEX_INITIALIZER 宏初始化靜態分配的互斥鎖
銷毀條件變量
用 int pthread_cond_destroy(pthread_cond_t *cond); 銷毀條件變量
- 參數:
- cond:要銷毀的條件變量的指針
取消阻塞一個條件變量線程
??用 int pthread_cond_signal(pthread_cond_t *cond); 取消阻塞一個條件變量線程。函數被用來釋放被阻塞在指定條件變量上的一個線程。必須在互斥鎖的保護下使用相應的條件變量。否則對條件變量的解鎖有可能發生在鎖定條件變量之前,從而造成死鎖。
- 參數:
- cond:指向已經被初始化了的條件變量的指針
- 返回值:如果成功返回0,失敗返回其他
- 注意:
- 可以使用 int pthread_cond_broadcast(pthread_cond_t *cond); 取消阻塞所有條件變量線程
- 由調度策略決定 pthread_cond_signal 喚醒那個線程
- 由調度策略決定 pthread_cond_broadcast 的喚醒順序
等待條件變量
用 int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); 等待條件變量
- 說明:
- 函數將解鎖 mutex 參數指向的互斥鎖,并使當前線程阻塞在 cv 參數指向的條件變量上。被阻塞的線程可以被 pthread_cond_signal 函數,pthread_cond_broadcast 函數喚醒,也可能在被信號中斷后被喚醒。
- pthread_cond_wait 函數的返回并不意味著條件的值一定發生了變化,必須重新檢查條件的值。
- pthread_cond_wait函數返回時,相應的互斥鎖將被當前線程鎖定,即使是函數出錯返回。
互斥鎖和條件變量使用實例
// producer_consumer.cpp // // 有一個生產者在生產產品,這些產品將提供給若干個消費者去消費,為了使生產者和消費者能并發執行, // 在兩者之間設置一個有多個緩沖區的緩沖池,生產者將它生產的產品放入一個緩沖區中,消費者可以從緩 // 沖區中取走產品進行消費,所有生產者和消費者都是異步方式運行的,但它們必須保持同步,即不允許消 // 費者到一個空的緩沖區中取產品,也不允許生產者向一個已經裝滿產品且尚未被取走的緩沖區中投放產品。 // #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define true 1 #define false 0 #define BUFFER_LENGTH 10 int buf[BUFFER_LENGTH]; // 緩沖區大小 int front = 0, rear = -1; // 緩沖區的前端和尾端 int size = 0; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 互斥鎖 mutex 使用宏賦值 pthread_cond_t empty_cond = PTHREAD_COND_INITIALIZER; // 條件變量 empty_cond 使用宏賦值 pthread_cond_t full_cond = PTHREAD_COND_INITIALIZER; // 條件變量 full_cond 使用宏賦值 int producer_wait = false; int consumer_wait = false; void *producer(void *arg); void *consumer(void *arg); int main(int argc, char **argv) {pthread_t producer_id;pthread_t consumer_id;pthread_create(&producer_id, NULL, producer, NULL); // 創建生產者線程,pthread_create(&consumer_id, NULL, consumer, NULL);sleep(1);return 0; } void *producer(void *arg) {pthread_detach(pthread_self()); // 分離自身,自動釋放所有資源while (true){pthread_mutex_lock(&mutex); // 上鎖,直到 pthread_mutex_unlock(&mutex);if (size == BUFFER_LENGTH) // 如果緩沖區已滿,等待; 否則,添加新產品{printf("buffer is full. producer is waiting...\n");producer_wait = true;pthread_cond_wait(&full_cond, &mutex); // 解開互斥鎖mutex,并由條件變量full_cond阻塞在此處(直到消費者調用pthread_cond_signal(&full_cond);解鎖),此時消費者可以訪問mutex,而不會阻塞了producer_wait = false;}// 往尾端添加一個產品rear = (rear + 1) % BUFFER_LENGTH;buf[rear] = rand() % BUFFER_LENGTH;printf("producer produces the item %d: %d\n", rear, buf[rear]);++size;if (size == 1) // 如果當前size=1, 說明以前size=0, 消費者在等待,則給消費者發信號{while (true){if (consumer_wait){pthread_cond_signal(&empty_cond); // 如果消費者在等待,則釋放消費者的阻塞狀態break;}}}pthread_mutex_unlock(&mutex);sleep(0.1);} } void *consumer(void *arg) {pthread_detach(pthread_self()); // 分離自身,自動釋放所有資源while (true){pthread_mutex_lock(&mutex);if (size == 0) // 如果緩沖區已空,等待; 否則,消費產品{printf("buffer is empty. consumer is waiting...\n");consumer_wait = true;pthread_cond_wait(&empty_cond, &mutex); // 解鎖 mutex,并阻塞在 empty_cond(直到生產者調用pthread_cond_signal(&empty_cond);解鎖),此時,生產者可以訪問mutex,而不會阻塞consumer_wait = false;}// 從前端消費一個產品printf("consumer consumes an item %d: %d\n", front, buf[front]);front = (front + 1) % BUFFER_LENGTH;--size;if (size == BUFFER_LENGTH-1) // 如果當前size=BUFFER_LENGTH-1,說明以前生產者在等待,則給生產者發信號{while (true){if (producer_wait){pthread_cond_signal(&full_cond);break;}}}pthread_mutex_unlock(&mutex);sleep(0.1);} }信號量(#include <semaphore.h>)
??信號量本質上是一個非負的整數計數器,它被用來控制對公共資源的訪問。當公共資源增加時,調用函數 sem_post() 增加信號量。只有當信號量值大于 0 時,才能使用公共資源,使用后,函數 sem_wait() 減少信號量。函數 sem_trywait() 和函數 pthread_ mutex_trylock() 起同樣的作用,它是函數 sem_wait() 的非阻塞版本。它們都在頭文件 semaphore.h 中定義。
??信號量的本質是一種數據操作鎖,它本身不具有數據交換的功能,而是通過控制其他的通信資源(文件,外部設備)來實現進程間通信,它本身只是一種外部資源的標識。信號量在此過程中負責數據操作的互斥、同步等功能。
信號量的數據類型為結構 sem_t,它本質上是一個長整型的數。
初始化
函數 int sem_init(sem_t *sem, int pshared, unsigned int value); 用來初始化一個信號量。
- 參數:
- sem:為指向信號量結構的一個指針;
- pshared:不為0時此信號量在進程間共享,否則只能為當前進程的所有線程共享;
- value:給出了信號量的初始值。
增加信號量
函數 int sem_post(sem_t *sem); 用來增加信號量的值。當有線程阻塞在這個信號量上時,調用這個函數會使其中的一個線程不在阻塞,選擇機制同樣是由線程的調度策略決定的。
減少信號量
函數 int sem_wait(sem_t *sem); 用來減少信號量的值。如果參數 sem 小于等于0,線程阻塞在此。直到信號量參數 sem 的值大于 0,解除阻塞后將 sem 的值減一,表明公共資源經使用后減少。函數sem_trywait ( sem_t *sem )是函數sem_wait()的非阻塞版本,它直接將信號量 sem 的值減一。
釋放信號量
函數 int sem_destroy(sem_t *sem); 用來釋放信號量 sem。
讀取信號量的值
用 int sem_getvalue(sem_t *sem,int *aval); 讀取當前信號量的值
信號量實例
#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <semaphore.h>sem_t sem; // 信號量void *thread1(); void *thread2(); int main() {pthread_t t1,t2;void *rtn1,*rtn2;sem_init(&sem,0,0); // 初始化信號量pthread_create(&t1, NULL, thread1, NULL); // 創建線程pthread_create(&t2, NULL, thread2, NULL);pthread_join(t1,&rtn1); // 等待線程結束pthread_join(t2,&rtn2); }void *thread1() {while(1){// pthread_detach(pthread_self());printf("Thread1:%u\n",pthread_self());sleep(1);sem_post(&sem); // 增加信號量} }void *thread2() {while(1){//pthread_detach(pthread_self());sem_wait(&sem); // 開始,信號量值為0,線程2阻塞在此處而不能運行printf("Thread2:%u\n",pthread_self());sleep(1);} }運行結果:
總結
以上是生活随笔為你收集整理的Linux 之二 Linux 多线程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux 之一 基本命令
- 下一篇: 华大 MCU 之四 使用问题记录