Linux下的多线程教程
目錄
1.線程技術起源
(1)線程和進程
(2)獲取線程ID實例
2.為什么要引入多線程
3.線程的實現
(1)注意事項
(2)創建線程
(3)拓展(線程結束的方式)
(4)初始線程和主線程
(5)線程的基本狀態
(6)線程取消
(7)向線程發送信號
(8)進程信號處理和線程信號屏蔽處理
(9)注冊和銷毀線程
(10)線程私有數據
4.修改線程的屬性
5.綁定屬性
6.分離屬性
7.線程棧的屬性
8.優先級設置
9.多線程訪問控制
(1)pthread_cond_init函數
(2)pthread_cond_wait()函數
(3)pthread_cond_timewait()函數
(4)pthread_cond_signal()函數
(5)拓展知識點
(6)互斥量屬性
(7)讀寫鎖?
(8)條件變量
10.綜合例子
11.一次性初始化
Linux中的Make工程管理
Linux中關于使用make管理工具的實例
Linux下的Makefile規則(隱式規則和模式規則)
1.線程技術起源
(1)線程和進程
- 線程技術早在20世紀60年代就被提出來了,但是真正的倍應用到操作系統中是在80年代中期。
| 線程 | 進程 | |
| 標識符類型 | pthread_t | pid_t |
| 獲取ID號 | pthread_self() | getpid() |
| 創建 | pthread_create() | fork() |
(2)獲取線程ID實例
makefile文件書寫:
提示:可以看到主函數中的進程ID號和父進程的ID號是一樣的,主函數下線程的ID號和打印函數下的線程ID號是不一樣的,說明兩個不同的線程共享了同樣的進程資源。?
2.為什么要引入多線程
- 由于在Linux下啟動一個新的進程必須分配給它獨立的地址空間,建立很多的數據表為維護它的代碼段,堆棧段和數據段,對于多任務工作來說是非常的“耗資源”的。
- 運行一個進程中的多個線程,彼此之間使用相同的地址空間,共享大部分數據,啟動一個線程所花費的空間遠遠小于啟動一個進程所花費的時間,并且線程之間彼此切換所需要的時間也遠遠小于進程之間切換的時間。
- 如下圖所示,可以看到進程之間都有獨立的自己地址空間,并且相隔比較疏遠;而對于同一個進程中的兩個線程來說,雖然這兩個線程之間保持獨立的寄存器和堆棧,但是它們之間關系緊密的多。也就是說進程為線程提供運行的資源并構成靜態環境,線程是處理機調度的基本單位。
- ?使用多線程方便線程之間進行通信
- 提高應用程序響應
- 使多CPU系統更加有效
- 改善程序結構。
3.線程的實現
(1)注意事項
- 包含頭文件pthread.h:Linux下的多線程遵循POSIX線程接口,稱為PTHREAD。
- 鏈接的時候需要使用庫文件:libpthread.a
(2)創建線程
#include<pthread.h> int pthread_create((pthread*thread,pthread_attr_t*attr,void*(*start_routine)(void*),void arg))- 第一個參數表示為指向線程標識符的指針;
- 第二個參數表示用來設置線程屬性;
- 第三個參數表示線程運行函數的起始地址;
- 最后一個參數表示就是運行函數的參數;
- 返回值:線程創建成功之后,函數返回0,否則返回錯誤的代碼:EAGAIN和EINVAL
(看了上面參數的解釋感覺還是不知道在講什么,到底是什么意思,繼續看面,后面通過實例來理解)。
例子:
C文件代碼:
- 參數解釋:
- 第一個參數為線程標識符指針;
- 第二個參數為NULL,表示生成默認屬性的線程;
- 第三個參數為運行函數thread的起始地址;
- 第四個參數由于函數thread沒有傳遞參數,所以為NULL;
- pthread_join:由于一個進程中的多個線程是共享數據段的,所以在線程退出之后,退出線程所占用的資源并不會隨著線程的終止而得到釋放,因此需要使用pthread_join將當前線程掛起,等待線程的結束。這個是一個線程阻塞的函數,調用它的函數將一直等待到被等待的線程結束為止,當函數返回時,等待線程的資源就被釋放。
- 原函數:int pthread_join(pthread_t th,void **thread_return);
- 第一個參數表示線程標識符指針;
- 第二個參數表示用戶定義的指針,可以用來存儲被等待線程的返回值。?
- 原函數:int pthread_join(pthread_t th,void **thread_return);
Makefile文件書寫:?
提示:如果不使用Makefile文件來管理,也可以直接輸入:gcc -lpthread pthread_Create.c -o pthread生成可執行文件pthread.?
Linux中的Make工程管理
Linux中關于使用make管理工具的實例
Linux下的Makefile規則(隱式規則和模式規則)
(3)拓展(線程結束的方式)
- 方法一:線程創建之后,就開始運行相關的線程函數,函數結束,調用它的線程也就結束;
- 方法二:通過函數pthread_exit實現結束
- void pthread_exit(void**retval)
- 該唯一的參數表示函數的返回代碼
- 當pthread_join中的第二個參數不是NULL的時候,這個值就會被傳遞給thread_return。
- 該唯一的參數表示函數的返回代碼
- int pthread_join(pthread_t tid,void**retval)(可以查看手冊:man pthred_join)
- 作用:調用該函數線程會一直阻塞,直到指定的線程tid調用pthread_exit或者從自動例程返回取消(exit(0))或者使用return返回。
- 參數解釋
- pthread_t:表示線程的標識符指針id;
- retval:表示碼,如果retval設置為PTHREAD_CANCELED,并且調用成功的話,那么返回0,否則返回錯誤碼。
- pthread_join會使得執行的線程處于分離的狀態。如果指定的線程已經是處于分離狀態了,那么調用該函數會失效,其中可以使用pthread_detach分離一個線程:int pthread_detach(pthread_t tid);
- void pthread_exit(void**retval)
例子1:使用三種不同的結束線程的方式,查看輸出的結果并總結其中的區別。
makefile文件書寫:
?提示:可以看到各個輸出的結果,使用pthread_exit和return返回語句的時候會等待主線程的結束,而使用exit的話不會等待主線程的結束直接終止。
例子2:使用pthread_join和pthread_detach實戰
首先知道怎么查看錯誤碼:sudo gedit /usr/inlcude/asm-generic/errno-bash.h
makefile文件書寫:
?
提示:可以看到thread2的retval2值為22,通過查看上面給出的錯誤表,可以看到22對應的為?:EINVAL
再查看pthread_join手冊:man pthread_join
?
從pthread_join手冊可以看到當調用了pthread_detach函數分離線程之后,再調用pthread_join(tid2,&retval2)連接的時候會出錯,并且退出碼返回的不是2,而是一個計算機中隨機的值。
(4)初始線程和主線程
1.初始線程:當程序執行時,首先運行在主函數中,而在線程代碼中,這個特殊的執行流程也稱為初始線程。
2.當主函數結束時,會導致進程結束,進程內所有的線程也都會結束。但是可以調用pthread_exit函數(后面詳解),等待所有的線程結束時進程才終止。
3.大多數情況下,主線程默認是在堆棧上運行的,該堆棧可以增長到足夠的長度,但是普通的堆棧是受限制的,一旦溢出將會產生錯誤。
例子1:定義一個結構體,并且創建一個線程,將結構體傳入,最后打印輸出主函數參數。
?
Makefile文件書寫:
拓展:可以上面這個例子在主函數的最后使用sleep進行了睡眠,但是如果將該睡眠放置在thread函數中會是什么結果呢?如下:
提示:從輸出的結果來判斷,由于在thread設置了睡眠,可以發現主函數在thread函數打印出之前就已經終止線程了,所以?thread函數還沒有來得及打印結果,線程就已經結束了。
那么解決這個問題了,這個時候就可以使用上面提到的pthread_exit函數讓等待所有的線程結束之后再終止。如下:
??
例子2:主函數打印奇數,創建的線程打印偶數。
??
(5)線程的基本狀態
- 就緒狀態:線程能夠運行,但是在等待可用的處理器(CPU);
- 運行狀態:線程正在運行,在多核系統中,可能同時有多個線程在運行;
- 阻塞狀態:線程在等待處理器以外的其他條件;
- 終止狀態:線程從啟動函數中返回,或者調用pthread_exit函數,或者被取消。
提示:如果讀者學習過操作系統的話,上面的知識點將非常容易理解。
(6)線程取消
- int pthread_cancel(pthread_t tid)
- 取消線程tid,取消成功則返回0;但是取消只是發送一個請求,并不意味著等待線程就會終止,而且發送成功也不是一定意味著線程tid一定會終止。
- 取消狀態
- int pthread_setcancelstate(int state,int*oldstate);
- 設置本線程對Cancel信號的反應,state有兩種值:
- PTHREAD_CANCEL_ENABLE(默認值):表示接收到信號后設置為CANCELED狀態。
- PTHREAD_CANCEL_DISABLE:表示忽略CANCEL信號繼續運行;
- old_state:如果不加NULL,則加入原來的Cancel狀態以便恢復;
- 返回值:如果取消成功,則返回0,否則返回錯誤碼!
- 設置本線程對Cancel信號的反應,state有兩種值:
- int pthread_setcancelstate(int state,int*oldstate);
- 取消類型
- int pthread_setcanceltype(int type,int *oldtype)
- 設置本線程取消動作的執行時機。
- type兩種值
- PTHREAD_CANCEL_DEFFERED:表示接收到信號后繼續運行下去直到下一個取消點再退出。
- PTHREAD_CANCEL_ASYCHRONOUS:表示立即執行取消動作,后面的程序代碼將不會繼續執行;
- old_state:如果不加NULL,則加入原來的Cancel狀態類型;
- 返回值:如果設置取消類型成功,則返回0,否則返回錯誤碼!
- int pthread_setcanceltype(int type,int *oldtype)
Makefile文件書寫:?
?提示:從退出碼的結果20來看,退出是正常的。
但是如果進行下面的改動之后:將取消線程的后面設置取消類型,這個取消類型設置為立即執行(也就是后面的操作不再執行)。
?
?提示:從輸出的結果來看,printf("finally!\n")沒有執行,并且退出碼也不是20,說明不是正常的退出,說明設置取消和設置取消類型成功。
(7)向線程發送信號
- int pthread_kill(phtread_t tid,int sig);
- phtread_kill不是終止線程的意思,表示向線程發送信號。
- 方式:向線程tid發送信號,如果線程代碼內不做處理,則按照信號默認的行為影響整個進程。也就是說如果給線程發送了一個信號,但是該信號沒有被處理,那么整個進程將推出。
- 參數sig:
- 如果sig為0的話,表示是一個保留的信號,用來判斷進行是否終止。
- 查看手冊:man pthread_kill。
- 返回值:如果是0,表示發送成功;否則返回錯誤碼。
例子:測試新建的線程結束之后,再向新建的線程發送一個0的信號,如果出錯的話,那么將打印出錯的信息。
提示:為什么是這樣的結果?主要是因為在主線程中睡眠了1秒,所以這個時候新線程已經執行完畢,那么再向新線程發送信息,將會出錯!?
(8)進程信號處理和線程信號屏蔽處理
- int sigaction(int signum,const struct sigaction*act,struct sigaction*oldact);
- 參數解釋:
- signum:給信號signum設置一個處理函數,而處理函數在sigaction中指定。
- sigaction:一個結構體,其中包含的內容如下:可以查看手冊man sigaction
- 其中sa_mask表示信號屏蔽字;sa_handle表示信號處理函數。
- 參數解釋:
- ing pthread_sigmask(int how,const sigset_t*set,sigset_t*oldset)
- 多線程信號屏蔽處理;
- 參數解釋:
- how=SIG_BLOCK:向當前的信號掩碼中添加set,其中set表示要阻塞的信號組;
- SIG_UNBLOCK:向當前的信號掩碼中刪除set,其中set表示要取消阻塞的信號組;
- SIG_SETMASK:將當前的信號掩碼替換為set,其中set表示新的信號掩碼。
例子:
Makefile文件書寫:
(9)注冊和銷毀線程
- 注冊處理程序:pthread_cleanup_push(void(*routine)(void),void*args);
- 清除處理程序:pthread_cleanup_pop(int execute);
- routine是處于棧頂的清除處理函數;
- args表示處理函數的參數;
- execute:表示當為非0的時候執行。
- 注意:清除的順序和入棧的順序是相反的(棧——后進先出)。
- 注意:這兩個函數必須同時使用。
例子:使用注冊處理函數和清除處理函數實現一個清除的過程。
提示:如果將pthread_cleanup_pop(0)參數修改為0的時候,是不執行清除操作的,但是如果修改成1的話,那么將執行清除操作:
提示:如果調用pthread_exit的話,也會響應清除請求,比將代碼修改為如下面:
??
(10)線程私有數據
在線程的私有數據之間,盡管名字是相同的,但是線程訪問的都是數據的副本。
?
首先需要創建一個與私有數據相關的鍵,獲取對私有數據的訪問權限。
- int pthread_key_create(pthread_key_t*key,void(*destructor)(void*));
- 其中創建的key放在指定的內存單元,?destructor是與鍵相關的析構函數(當調用pthread_exit或者return的時候,會調用析構函數)。當析構函數被調用的時候,只有一個參數,就是與key關聯的數據地址。
- 使用鍵可以和私有數據進行關聯,之后就可以通過鍵來查找數據,所有的線程都可以訪問創建的鍵。
- 將鍵與私有數據進行關聯
- void*pthread_setspecific(pthread_key_t key,const void*value);
- 獲取私有數據,如果沒有關聯,則返回空
- void*pthread_getspecific(pthread_key_t key);
?
?
?
提示:可以設置不同的鍵值,之間是不相互影響的。?
4.修改線程的屬性
提示:對于大部分創建線程的情況,使用默認的屬性即可。
- 屬性結構為pthread_attr_t
- 初始化函數為pthread_attr_init:該函數必須在創建線程之前調用;
- 屬性對象
- 是否綁定
- 是否分離
- 堆棧大小
- 堆棧地址
- 優先級
- 默認屬性為
- 非綁定
- 非分離
- 缺省1MB的堆棧和父進程同樣的優先級別
5.綁定屬性
線程的綁定需要:輕進程LWP(Light Weight Process)
- 位于用戶層和系統層之間
- 系統對于線程資源的分配和對線程的控制是通過輕進程實現的
- 一個輕進程可以控制一個或者多個線程
- 非綁定:默認屬性情況下,啟動多少輕進程,哪些輕進程來空間哪些線程是由系統來控制的(非綁定)
- 綁定:某個線程固定的“綁”在一個輕進程之上,并且被綁定的線程具有較高的響應速度。
- pthread_attr_setscope參數解釋
- 第一個參數為屬性結構的指針;
- 第二個參數為綁定類型,其中的PTHREAD_SCOPE_SYSTEM(綁定),PTHREAD_SCOPE_PROCESS(非綁定)。
6.分離屬性
分離屬性決定一個線程 以什么樣的方式終止:
- 默認屬性情況下為非分離屬性,也就是原有的線程等待創建的線程結束;
- 默認情況下只有當pthread_join函數返回時,創建的線程才算終止,才能釋放自己的資源。
- 如果設置為分離的屬性的話,不會被其他的線程所等待,自己運行結束了,線程也就結束了,馬上釋放資源
- void pthread_attr_setdetachstate(pthread_attr_t*attr,int detachstate)
- 參數解釋:
- 第一個參數為屬性結構的指針;
- 第二個參數可為:
- PTHREAD_CREATE_DETACHED(分離屬性)
- PTHREAD_CREATE_JOINABLE(非分離屬性)
- 參數解釋:
- 設置了分離屬性之后,該線程會運行很快。
- 注意:
- 由于設置了分離屬性之后線程運行的很快,所以很有可能在pthread_create之前就可以終止并釋放資源,那么會導致pthread_create線程得到錯誤的代碼,所以解決方法是采取同步的措施。
- 在被創建的線程里調用pthread_cond_timewait函數,讓這個線程等一會,使得pthread_create有足夠的時間返回。
- 不要使用wait()方法,因為這樣會使得整個線程處于睡眠,不能解決線程同步的問題。
- 由于設置了分離屬性之后線程運行的很快,所以很有可能在pthread_create之前就可以終止并釋放資源,那么會導致pthread_create線程得到錯誤的代碼,所以解決方法是采取同步的措施。
- 獲得分離屬性:int pthread_attr_getdetachstate(pthread_attr_t*attr,int detachstate);
- 初始化:int pthread_attr_init(pthread_attr_t*attr);
- 銷毀:int pthread_attr_destroy(pthread_attr_t*attr);
- 設置分離屬性的步驟:
- 定義線程屬性變量:pthread_attr_t attr;
- 初始化attr:pthread_attr_init(&attr);
- 設置線程為分離屬性或者非分離屬性:pthread_attr_setdetachstate(&attr,detachstate);
- 最后創建線程:pthread_create(&tid,&attr,thread,NULL);
例子:通過設置分離屬性和一個非設置分離屬性(默認)線程,輸出查看設置分離屬性和非分離屬性的結果。
??
提示:可以看到線程1在設置了分離的屬性之后,連接是失敗的。?
7.線程棧的屬性
對于線程,虛擬地址空間大小是固定的,進程只有一個棧;但是對于線程,同樣的虛擬地址被所有的線程共享,如果應用程序使用了太多的線程,那么導致線程累計和超過可用的虛擬地址空間,那么這個時候需要減少線程默認的棧大小;但是如果線程分配了大量的自動變量或者線程的棧太深,那么這個時候需要使得默認的棧更大。
- 引入頭文件<limits.h>
- 查看棧的大小:ulimit -s
- 修改棧屬性
- int pthread_attr_setstack(pthread_attr_t*attr,void*stackaddr,size_t stacksize);
- 獲取棧屬性
- int pthread_attr_getstack(pthread_attr_t*attr,void*stackaddr,size_t stacksize);
- 其中stackaddr表示棧的內存單元最低地址;
- stacksize表示棧的大小;
- 設置棧的大小
- int pthread_attr_setstacksize(pthread_attr_t*attr,size_t stacksize);
- 獲取棧的大小
- int pthread_attr_getstacksize(pthread_attr_t*attr,size_t stacksize);
- 檢查是否可以修改棧的屬性
- _POSIX_THREAD_ATTR_STACKADDR和_POSIX_THREAD_ATTR_STACKSIZE用來檢查系統是否支持線程屬性(宏定義的位置:/usr/include/bits/posix_opt.h)
- 修改棧屬性
- 設置棧溢出警戒區
- 設置線程棧末尾
- int pthread_attr_setguardsize(pthread_attr_t*attr,size_t guardsize);
- 獲取線程棧末尾
- int pthread_attr_getguardsize(pthread_attr_t*attr,size_t guardsize);
- 設置線程棧末尾guardsize主要是避免棧溢出的擴展內存大小,默認是PAGESIZE個字節,如果設置為0,就不提供警戒緩沖區。
- 設置線程棧末尾
?
8.優先級設置
比較常用的屬性是線程的優先級
- 存放的結構:sched_param
- 調用函數:pthread_attr_getschedparam和pthread_attr_setschedparam.
- 總是首先獲取優先級,獲取優先級之后再是設置優先級。
9.多線程訪問控制
由于多線程共享同一個進程的資源和地址,因此對這些資源的操作,必須要考慮線程之間資源訪問的唯一性問題。
- 線程同步可以使用互斥鎖和信號量機制(操作系統之間有詳細講解)來解決線程之間數據的共享和通信問題。
- 互斥鎖
- 鎖定和非鎖定
- 條件變量通過允許線程阻塞和等待另一個線程發送信號的方法彌補了互斥鎖的不足。
- 當條件變量被用來阻塞一個線程,當條件不滿足時,線程解開相應的互斥鎖并等待條件發生變化。
- 一旦其他的某個線程改變了條件變量,將通知相應的條件變量喚醒一個或者多個正在被此條件變量阻塞的線程。
- 這些線程將重新鎖定互斥鎖并重新測試條件是否滿足。
例子:假設兩個線程沒有同步,輸出結果:
??
(1)pthread_cond_init函數
- 函數條件變量的結構為pthread_cond_t,函數pthread_cond_init()用來初始化條件變量
- int pthread_cond_init(pthread_cond_t*cond,__const pthread_condattr_t*cond_attr);
- 參數解釋
- 第一個參數為指向結構pthread_cond_t的指針;
- 第二參數cond_attr指向一個結構pthread_condattr。
- pthread_condattr是條件變量的屬性結構,與互斥鎖一樣可以用來設置條件變量是進程內還是進程間可用,默認屬性為PTHREAD_PROCESS_PRIVATE,表示被同一進程內的各個線程使用。
- 注意:當初始化條件變量只有未被使用時才能重新初始化或者被釋放。
(2)pthread_cond_wait()函數
- 使得線程阻塞在一個條件變量上面
- extern int pthread_cond_wait(pthread_cond_t*__restrict__cond,pthread_mutex_t*__restrict_mutex)
- 線程解開mutex指向的鎖并被條件變量cond阻塞。
- 線程可以被函數pthread_cond_signal和函數pthread_cond_broadcast喚醒,其中條件變量只是起喚醒和阻塞的作用,具體的判斷條件需要自己給出。
(3)pthread_cond_timewait()函數
- 用來阻塞線程
- extern int pthread_cond_timewait__P((pthread_cond_t*__cond,pthread_mutex_t*__mutex,__const struct timespec*__abstime))
- 相比于pthread_cond_wait函數而言,多了一個時間參數,經歷了abstime時間之后,即使條件變量不滿足,阻塞也將被解除。
(4)pthread_cond_signal()函數
- 用來阻塞在條件變量cond上的一個線程。
- extern int pthread_cond_signal(pthread_cond_t*__cond)
- 決定哪個線程被喚醒,是有線程的調度策略決定的。
- 必須使用保護條件變量的互斥鎖來保護這個函數,否則條件滿足信號可能在測試條件和調用pthread_cond_wait函數之間被發出,造成無限制的等待。
(5)拓展知識點
- 獲得父進程的ID:
- pthread_t pthread_self(void);
- 測試兩個線程號是否相同:
- int pthread_equal(pthread_t __thread1,pthread_t __thread2);
- 互斥量初始化:
- pthread_mutex_init(pthread_mutex_t*,__const pthread_mutexattr_t*);
- 如果是動態分配的互斥量使用此方式進行初始化。
- 如果是靜態分配的互斥量,將它設置為常量:PTHREAD_MUTEX_INITIALIZER
- 第一個參數為初始化的互斥量,第二個參數為屬性,通常為NULL(默認)。
- 銷毀互斥量:
- int pthread_mutex_destry(pthread_mutex_t*__mutex);
- 如果是動態分配的互斥量使用此方式進行銷毀。
- 獲得互斥量的鎖定(非阻塞):
- int pthread_mutex_trylock(pthread_mutex_t*__mutex);
- 返回值:如果為0,表示成功,否則返回錯誤碼。
- 如果互斥量已經被鎖住,那么不會導致該線程被阻塞。
- 鎖定互斥量(阻塞):
- int pthread_mutex_lock(pthread_mutex_t*__mutex);
- 返回值:如果為0,表示成功,否則返回錯誤碼。
- 如果互斥量已經被鎖住,那么會導致該線程被阻塞。
- 解鎖互斥量:
- int pthread_mutex_unlock(pthread_mutex_t*__mutex);
例子1:依然是上面的一個例子進行修改。
(6)互斥量屬性
?線程互斥量屬性pthread_mutexattr_t類型的數據表示。
- 互斥量初始化
- int pthread_mutexattr_init(pthread_mutexattr_t*attr);
- 互斥量銷毀
- ?int pthread_mutexattr_destroy(pthread_mutexattr_t*attr);
進程共享屬性兩種值:
- PTHREAD_PROCESS_PRIVATE:默認值,同一個進程中的多個線程訪問同一個同步對象。
- ?PTHREAD_PROCESS_SHARED:可以使得互斥量在多個進程中進行同步,如果互斥量在多進程的共享內存區域,那么這個屬性的互斥量可以同步多進程。
- 設置互斥量進程共享屬性
- int pthread_mutexattr_getshared(const pthread_mutexattr_t*restrict attr,int *restrict pshared);
- ?int pthread_mutexattr_setshared(const pthread_mutexattr_t*attr,int * pshared);
- 檢查共享屬性否支持:_POSIX_THREAD_PROCESS_SHARED
| 互斥量類型 | 沒有解鎖時再次加鎖 | 不占用解鎖 | 已解鎖時再次解鎖 |
| PTHREAD_MUTEX_NORMLA | 死鎖 | 未定義 | 未定義 |
| PTHREAD_MUTEX_ERRORCHEK | 返回錯誤 | 返回錯誤 | 返回錯誤 |
| PTHREAD_MUTEX_RECURSIVE | 允許 | 返回錯誤 | 返回錯誤 |
| PTHREAD_MUTEX_DEFAULT | 未定義 | 未定義 | 未定義 |
- 設置互斥量的類型屬性
- int pthread_mutexattr_gettype(const pthread_mutexattr_t*restrict attr,int *restrict type);
- int pthread_mutexattr_settype(const pthread_mutexattr_t* attr,int ?type);
注意下面的程序編譯方式:sudo gcc -o pDemo01 pDemo01.c -lrt
??
?
使用Makefile文件:
?
?
(7)讀寫鎖?
- 初始化讀寫鎖: int pthread_rwlock_init(pthread_rwlock_t*restrict rwlock,const pthread_rwlockattr_t*restrict attr);
- 返回值:如果為0,表示成功,否則返回錯誤碼。
- 讀模式加鎖:
- int pthread_rwlock_rdlock(pthread_rwlock_t*rwlock);
- int pthread_rwlock_tryrdlock(pthread_rwlock_t*rwlock);
- 寫模式加鎖:
- int pthread_rwlock_wrlock(pthread_rwlock_t*rwlock);
- int pthread_rwlock_trywrlock(pthread_rwlock_t*rwlock);
- 解鎖:
- int pthread_rwlock_unlock(pthread_rwlock_t*rwlock);
- 銷毀讀寫鎖:int pthread_rwlock_destroy(pthread_rwlock_t*rwlock);
- 返回值:如果為0,表示成功,否則返回錯誤碼。
例子:驗證兩個線程可以同時讀鎖。
?提示:
1.可以看到當線程1擁有了讀鎖之后,線程2也可以擁有讀鎖。
2.但是對于寫加鎖狀態的話,當兩個線程都是寫狀態的時候,只能有一個線程在執行,只有當該線程執行完畢之后,那么另一個線程才能開始執行。
3.如果是一個寫加鎖,另一個是讀加鎖的話,也是只能有一個線程在執行,只有當該線程執行完畢之后,那么另一個線程才能開始執行。
(8)條件變量
- 條件變量初始化:
- int pthread_cond_init(pthread_cond_t*__restrict __cond,__const pthread_condattr_t*__restrict_cond_attr);
- pthread_cond_t cond=PTHREAD_COND_INITLALIZER;
- 銷毀條件變量COND:
- int pthread_cond_destroy(pthread_cond_t*__cond);
- 喚醒線程等待條件變量:
- 至少喚醒等待條件的某一個線程:int pthread_cond_signal(pthread_cond_t*__cond);
- 喚醒等待條件的所有線程:int pthread_cond_broadcast(pthread_cond_t*__cond);
- 等待條件變量(阻塞):
- int pthread_cond_wait(pthread_cond_t*__restrict __cond,pthread_mutex_t* __restrict_mutex);
- 使用等待條件變量為真,傳遞給pthread_cond_wait的互斥量對條件進行保護,調用為者把鎖住的互斥量傳遞給函數;
- 將線程放到等待條件的線程列表上,然后對互斥量進行解鎖,當條件滿足這個函數返回,返回后進行對互斥量進行加鎖。
- 在指定時間到達前等待條件變量:
- int pthread_cond_timewait(pthread_cond_t* __restrict_cond,pthread_mutex*__restrict__mutex,__const struct timepec*__restrict__abstime);
- timewait指定時間,如果到了指定的時間條件還是不滿足的話,那么就返回。
- timewait的結構體內容如下:
- time_t tv_sec;
- long_t tv_nsec;
例子1:交替打印奇數和偶數
?
??
例子2:下面的綜合例子消費者和生產者就是最好的應用。
10.綜合例子
著名的生產者-消費者問題模型的實現,主程序中分別啟動生產者線程和消費者 線程。生產者線程不斷順序地將 0 到 50?的數字寫入共享的循環緩沖區,同時消費者線程 不斷地從共享的循環緩沖區讀取數據。
#include <stdio.h> #include <stdlib.h> #include <time.h> #include <pthread.h> #define BUFFER_SIZE 16 /* 設置一個整數的圓形緩沖區 */ struct prodcons {int buffer[BUFFER_SIZE]; /* 緩沖區數組 */pthread_mutex_t lock; /* 互斥鎖 */int readpos, writepos; /* 讀寫的位置*/pthread_cond_t notempty; /* 緩沖區非空信號 */pthread_cond_t notfull; /*緩沖區非滿信號 */ }; /*--------------------------------------------------------*/ /*初始化緩沖區*/ void init(struct prodcons * b) {pthread_mutex_init(&b->lock, NULL); pthread_cond_init(&b->notempty, NULL); pthread_cond_init(&b->notfull, NULL);b->readpos = 0;b->writepos = 0; } /*--------------------------------------------------------*/ /* 向緩沖區中寫入一個整數*/ void put(struct prodcons * b, int data) {pthread_mutex_lock(&b->lock);/*等待緩沖區非滿 when the buffer is not full and you can write a number*/while ((b->writepos + 1) % BUFFER_SIZE == b->readpos) {printf("wait for not full\n");pthread_cond_wait(&b->notfull, &b->lock);}/*寫數據并且指針前移*/b->buffer[b->writepos] = data;b->writepos++;if (b->writepos >= BUFFER_SIZE) b->writepos = 0;/*設置緩沖區非空信號*/pthread_cond_signal(&b->notempty);pthread_mutex_unlock(&b->lock); } /*--------------------------------------------------------*/ /*從緩沖區中讀出一個整數 */ int get(struct prodcons * b) {int data;pthread_mutex_lock(&b->lock);/*等待緩沖區非空when the buffer is not empty and you can read a number*/while (b->writepos == b->readpos) { printf("wait for not empty\n"); pthread_cond_wait(&b->notempty, &b->lock);}/* 讀數據并且指針前移 */ data = b->buffer[b->readpos]; b->readpos++;if (b->readpos >= BUFFER_SIZE) b->readpos = 0;/* 設置緩沖區非滿信號*/pthread_cond_signal(&b->notfull);pthread_mutex_unlock(&b->lock);return data; } /*--------------------------------------------------------*/ #define OVER (-1) struct prodcons buffer; /*--------------------------------------------------------*/ void * producer(void * data) {int n;for (n = 0; n < 50; n++) { printf(" put-->%d\n", n); put(&buffer, n);}put(&buffer, OVER); printf("producer stopped!\n"); return NULL; } /*--------------------------------------------------------*/ void * consumer(void * data) {int d;while (1) {d = get(&buffer);if (d == OVER ) break;printf(" %d-->get\n", d);}printf("consumer stopped!\n");return NULL; } /*--------------------------------------------------------*/ int main(void) {pthread_t th_a, th_b; void * retval; init(&buffer);pthread_create(&th_a, NULL, producer, 0);pthread_create(&th_b, NULL, consumer, 0);/* 等待生產者和消費者結束 */ pthread_join(th_a, &retval); pthread_join(th_b, &retval); return 0; }11.一次性初始化
像互斥量只能初始化一次,一般的初始化方法都是放在主函數main中,但是對于庫函數不能這樣做,如果要實現一次性初始化,可以使用下面的方法:
- 首先定義一個pthread_once_t類型的變量,使用PTHREAD_ONCE_INIT進行初始化
- pthread_once_t once=PTHREAD_ONCE_INIT;
- 初始化函數書寫:
void init_routine(){
? ? ? ? //初始化互斥量
? ? ? ? //初始化讀寫鎖
}
- 調用函數
- int pthread_once(pthread_once_t*once_control,void(*init_routine)(void))
- 函數功能:該函數使用PTHREAD_ONCE_INIT初始化once_control變量,保證init_routine函數在本進程中執行時,僅僅執行一次。在多個線程的情況下,即使pthread_once函數被調用了多次,init_routine函數也只會執行一次。
- PTHREAD_ONCE_INIT如果為0,表示pthread_once從沒有執行過,init_routine函數就會執行;
- PTHREAD_ONCE_INIT如果為1,表示pthread_once都必須等待其中一個激發“已經執行一次”信號,因此所有的pthread_once都會處于阻塞當中,init_routine函數就不會執行;
- PTHREAD_ONCE_INIT如果為2,表示pthread_once已經執行過一次了,init_routine函數以后都不會執行了;
?提示:如果將once的值設置為1,那么將處于阻塞狀態。
教程主要來源
《嵌入式Linux操作系統應用于開發》
【linux多線程編程】
總結
以上是生活随笔為你收集整理的Linux下的多线程教程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux系统下c 开发视频教程,【C/
- 下一篇: Linux系统如何重装Windows系统