日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux编程---线程

發布時間:2025/5/22 linux 12 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux编程---线程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

首先說一下線程的概念.事實上就是運行在進程的上下文環境中的一個運行流.普通進程僅僅有一條運行流,可是線程提供了多種運行的路徑并行的局面.

同一時候,線程還分為核心級線程和用戶級線程.主要差別在屬于核內還是核外.

核心級線程,地位基本和進程相當,內核調度.也就是說這樣的系統時間片是按線程來分配的.這樣的線程的優點就是能夠適當的運用SMP,即針對多核CPU進行調度.

用戶級線程,在用戶態來調度.所以相對來說,切換的調度時間相對核心級線程來說要快不少.可是不能針對SMP進行調度.

?

對于如今的系統來說,純粹的用戶級線程僅僅存在于實驗室中吧.

對于Linux中實現的線程來說,支持LWP(輕量級進程).把用戶級線程映射到內核級上.簡單來說,Linux僅僅有內核級進程,不存在真正的線程.可是我們也能夠把LWP叫做線程.

?

線程和進程對操作系統來說相當于是平行的關系,但大部分資源是隸屬于進程的.假設進程掛了,所有線程也會終止.(直接賦值其進程的頁表,然后改動特殊的部分就可以創建一下線程~復習一下虛擬存儲~)

?

順帶一提erlang虛擬機下的輕量級線程是虛擬機下實現的。應該叫進程。.我記得在哪看過,erlang切換線程比c運行的線程切換還快.應該能夠分類成用戶級線程吧。

Linux僅僅有LWP..

?

這里我還有個疑問,就是Linux的線程是否是環保線程.這個概念也是今天剛看<現代操作系統>中提到的.意思就是對于進程來說,線程并不馬上釋放資源,而是等到進程結束再釋放.這樣就省去了線程又一次生成的開銷.對于server來說應該是非常實用的一個策略呢.有知道的嗎?

?--------------------補充說明切割線-----------------------

又看了些書.發現實際上linux的線程創建過程主要調用的是clone函數.

這個函數的第二個參數有好幾種狀態選擇.這些選擇決定了clone出來的進程是一般所說的線程還是一個進程.

而且有下面幾種標志能夠選擇:

CLONE_VM 置1:創建線程--共享地址空間???置0:創建進程,不共享地址空間,但會復制

CLONE_FS 共享umask?不共享

CLONE_FILES 共享文件描寫敘述符 拷貝文件描寫敘述符

CLONE_SIGHAND 共享信號句柄表 賦值信號句柄表

CLONE_PID 新線程獲得舊的PID?新線程獲得自己的PID

CLONE_PARENT 新線程與調度這有同樣的父親 新線程的父親是調用者

Linux對進程標識符PID和任務標識符TID進行了區分!!而且兩個都在task_struct結構中.

當用clone函數創建一個新進程而不須要和舊進程共享不論什么信息時,PID被設置成一個新值(新進程?fork?

).否則任務得到一個新的任務標識符,可是PID不變.

TID也就是我后面會說的線程標識符.

預計pthread庫中,應該就是把這些標志都選上,然后創建的.

----------------------------------------------------------------

?

使用線程的程序一般具有一下特征:

1.可以與其它任務并行運行.

2.有可能會被堵塞較長時間,但這時候其它進程可并發運行.

3.須要回應異步事件.畢竟異步本身就是不確定堵塞時間的.

4.線程使用CPU的時間足夠長.不然切換的代價也不少.

?

這里要寫的是Pthread線程.也就是POSIX定義的線程接口.這個接口包括100多個函數,全部函數名都已pthread_開頭.功能上大致分為三類:

線程管理:?這類函數負責線程的創建,終止,匯合,取消,以及線程屬性的設置和查詢.

線程同步:?Pthread提供了相互排斥變量,條件變量,柵欄(屏障)變量等手段支持線程間的同步.

操作線程專有數據:?多線程程序中,全局數據分為全部線程都能夠訪問的共享數據和單個線程內全部函數都能夠訪問的線程專有數據.

?

這里要注意一點:差點兒全部函數都部通過errno來報錯.運行成功均返回0.除開pthread_getspecific全然不報錯之外,其余的返回錯誤號.

可是對于單獨的一個線程,報錯的時候改動你的errno,其它線程是無法干擾的.實際上Linuxerrno是一個局部變量(這里也是網上查的,只是都是非常老的帖子里面的,有錯誤請指正)

?

線程標識:

pthread_t?pthread_self(void);???用于獲得線程ID,TID

pthread_equal(pthread_t?t1,pthread_t?t2);???用于比較兩個線程標號,pthread_t可能并非整形.

?

?

.線程管理

1.創建線程

int?pthread_create(pthread_t?*restrict?thread,const?pthread_attr_t?*restrict?attr,void?*(*start_routine)(void*),void?*?restrict?arg);

第一個參數傳回其線程的TID,第二個參數用來設置線程的屬性,一般寫NULL,第三個為線程開始運行的函數指針,第四個參數為傳遞給線程的唯一一個參數.

?

注意Pthread的返回值是統一的.之后都不會再說了.

?

2.終止線程

int?pthread_exit(void?*value_ptr);

這個函數是用于線程自己終止自己的.當中的參數相當于是返回值,當還有一個線程調用pthread_join()等待其結束時,這個值會給pthread_join中的參數.

這里須要注意的就是,不要讓value_ptr指向局部變量,由于線程結束,其資源會被回收.最好養成用malloc直接申請一個的習慣.免得在線程分離的情況下出現故障.(假設Linux是環保線程的話,那么這個就不用操心太多了.)

同一時候,線程的回收并不回收不論什么進程相關的資源.

?

假設你用exit()的話,那么整個進程都會被回收..而不是回收線程.而且注意,假設main線程結束了的話,那么線程也不運行了!!!假設你想讓線程運行完再結束進程,一定要設置好同步!!pthread_join就非常好.

?

3.等待線程終止

int?pthread_join(pthread_t?thread,void?**value_ptr);

第一個參數是指定等待線程的TID.第二個則是調用pthread_exit返回的參數.

而且這個函數是會堵塞的!這個函數類似進程中的wait函數,還具有釋放資源的功能.這還涉及到一個可匯合與分離的線程概念.

可匯合的意思就是說,線程的資源在返回給pthread_join之后才釋放.

分離的意思就是資源的釋放有系統來搞定.

這樣做的目的是盡可能的節省系統資源,提高效率.可能一般小程序體現不出什么,可是對于server程序來說,一點性能的提升就能干非常多事~

?

這里的join僅僅能用于可匯合的線程,分離線程則不行.

默認的線程創建出來的都是可匯合的.

?

另一點,線程是能夠互相等待的.并非僅僅有主線程才干等待其它線程.不論什么線程都能夠等待另外一個線程的結束.

?

4.分離線程

int?pthread_detach(pthread_t?thread);

指定一個TID,然后這個線程就分離了.so?easy...

當然,你得細致考慮這個線程是否須要返回才是.

假設調用了兩次的話,那么Linux下會返回EINVAL.

分離之后線程就不由父線程管了.pthread_join就不會等待它了.

?

注意,分離線程的TID在線程終止后能夠馬上又一次分配給其它創建的線程.

當應用程序須要與分離的線程同步,應當現判別該線程是否已經終止.能夠用全局變量來當作標志.避免分離的進程隨主線程的exit而終止.

?

5.創建特殊的線程

這個是補充之前的創建線程的.

這里用到了非常多函數...我就不明確為什么不直接用結構體賦值取代...非要搞函數..

1)線程屬性對象的初始化和銷毀函數

int?pthread_attr_init(pthread_attr_t?*attr)

int?pthread_attr_destroy(pthread_attr_t?*attr)

初始化包括兩件事:分配空間,賦初始值.所以非常明顯destroy是用來釋放空間的.

注意這里傳值傳的是一個指針,而不是結構體!

?

2)線程分離狀態的查詢與設置函數

int?pthread_attr_getdetachstate(pthread_attr_t?*attr,int?*detachstate);

int?pthread_attr_setdetachstate(pthread_attr_t?*attr,int?*detachstate);

get返回之后,設置第二個參數為PTHREAD_CREATE_DETACHED或者PTHREAD_CREATE_JOINABLE.意思非常明顯了.

set則能夠通過第二個參數設置.選項就是上面兩個.

?

3)線程棧的查詢和設置函數

int?pthread_attr_getstacksize(const?pthread_attr_t?*restrict?attr,size_t?*restrict?stacksize);

int?pthread_attr_setstacksize(pthread_attr_t?*attr,size_t?stacksize);

第二個參數同上,一個取值,一個設置.而且設置的時候值不能小于PTHREAD_STACK_MIN

這里書上沒有說,可是我查的是按字節來分配的.通常Linux的棧大小為10M.

?

int?pthread_attr_getstackaddr(const?pthread_attr_t?*restrict?attr,void?**restrict?stackaddr);

int?pthread_attr_setstackaddr(const?pthread_attr_t?*restrict?attr,void?*stackaddr);

這個就是獲得和設置棧的虛擬地址沒多少可說的...

這里要注意的就是:

--對于多個線程,不要用統一的屬性來設置地址.畢竟虛擬地址空間是統一的.

--這個線程應當是可匯合的.(這里我不太明確為什么應該這樣,詳細看源代碼再說吧)

--這個線程棧的保護區應當是由自己設置的.

--內存對齊.

?

int?pthread_attr_getstack(const?pthread_attr_t?*restrict?attr,void?**restrict?stackaddr,size_t?*restrict?stacksize);

int?pthread_attr_setstack(const?pthread_attr_t?*restrict?attr,void?*restrict?stackaddr,size_t?restrict?stacksize);

因為分別獲取或設置效率較低,所以干脆一次性來獲取或設置..

?

4)棧一處保護區大小的查詢和設置函數

int?pthread_attr_getguardsize(const?pthread_attr_t?*restrict?attr,size_t?*restrict?guardsize);

int?pthread_attr_setguardsize(pthread_attr_t?*attr,size_t?guardsize);

棧保護區是在線程棧之后的一片區域,而且設置了特殊處理假設產生棧溢出的話,那么線程就會收到一個SIGSEGV信號.

通常有兩種情況須要調整其大小:

--節省存儲空間.對于嵌入式設備比較實用吧.

--線程須要更大的空間存放局部變量時.比方用到了深度遞歸程序的話~(預計線程的進入函數也能遞歸~)

?

6.取消線程

與之前的線程用exit終止自己不一樣,這個是一個線程來取消另外一個線程.對于一些并行編程應該非常有作用吧.比方你用BFS搜索一個解空間,每一個線程分配一個搜索范圍.搜索到答案之后,其余在搜索解的線程就能夠取消掉了.

同一時候,這個取消是”友好的”,意思就是并非強行讓線程終止.就和IO差點兒相同.有一個緩存的過程.自己能夠去查看是否緩存好,也能夠通過異步來實現.

?

可取消的屬性

--可取消狀態:這個表示能否夠被其它線程取消.默認屬性是同意的.

--可取消類型:默認是延遲取消.也就是線程自己檢查是否可被取消.這就類似同步IO,必須自己去檢查是否有數據來.為了效率還有第二種異步類型.

?

假設要改變默認值,能夠用以下兩個函數:

int?pthread_setcancelstate?(int?state,int?*oldstate);

int?pthread_setcanceltype?(int?tyep,int?*oldtype);

第一個參數都是新的設置,第二個則是原來的設置值.

state能夠取值為PTHREAD_CANCEL_ENABLEPTHREAD_CANCEL_DISABLE

type能夠取值為PTHREAD_CANCEL_DEFERREDPTHREAD_CANCEL_ASYNCHRONOUS.

注意,假設對一個不同意取消的線程發送一個請求,那么請求會保持.這一點和信號不同.

?

明白了屬性,以下再來介紹實際操作.

int?pthread_cancel(pthread_t?target_thread);

這個是發送取消請求.參數是線程ID.而且不等待目標線程的終止.

?

延遲類型下,分散在非常多地方..比方各種系統調用和phread函數中都會檢查的.這些個函數太多了,我就不細寫了....詳細能夠查手冊.只是能夠知道的是,這些函數中多數為慢系統調用(有可能被無限堵塞).

?

延遲類型還能夠用專門的函數來檢查:

void?pthread_testcancel();

檢查到了之后直接取消線程.

?

異步類型就是隨便什么時候終止都行了.這就必需要需要保證相互排斥量,線程專有數據,堆空間什么的沒在使用了.

當然,為了異步編程方便,實際上在每一個線程中有一個隱含的清理器棧,就是個函數指針棧.每次取消線程或者退出線程的時候會自己主動運行這些棧中的函數.在必要時,也能夠自己彈出棧,然后運行.

void?pthread_cleanup_push(vodi?(*routine?)(void?*),void?*arg);

void?pthread_cleanup_pop(int?execute);

第一個函數是入棧.其第二個參數是在第一個參數運行時,送給第一個參數的的參數(好繞)....

第二個函數則是從其棧中彈出.當參數不是0的時候,會運行!

?

感覺這兩個函數還蠻方便的.比析構好用多了.

?

7.線程調度?

1)線程調度范圍

POSIX定義了兩種調度模式:進程調度模式和系統調度模式.前者的競爭范圍為進程內,后者則為進程間.感覺就是所謂的用戶級線程和內核級線程.Linux僅僅有系統調度模式.APUE也沒有相關的內容.以后做了實驗再來補一篇博客吧.

PTHREAD_SCOPE_PROCESS表示進程調度模式.

PTHREAD_SCOPE_SYSTEM?表示系統調度模式.

這么分,主要是為了區分線程競爭的對手.前者僅僅在進程內區分.后者則和其它進程一起競爭.

主要在phread_attr_t類型變量中設置后,傳遞給pthread_create.

?

2)線程調度策略與優先級

SCHED_FIFO?基于優先級的先進先出調度策略.不同優先級不同隊列.這樣的策略可能讓高優先級線程獨占系統.

SCHED_RR?循環調度.也就是時間片輪轉.也有FIFO的優先級隊列.盡管是時間片輪轉,但仍有可能獨占系統.

SCHED_OTHER?這樣的一般類UNIX系統默覺得對進城的系統分時調度策略.這隨系統不同,策略也不同,所以不可移植.當然,也能夠採用自定義的策略.

?

int?sched_get_priority_max(int?policy);

int?sched_get_priority_min(int?policy);

兩個函數能夠用來得到優先級的最大值和最小值.都是個int類型.

參數的意思就是上面所說的策略.把宏定義填進去就能夠了.

?

POSIX沒有對SCHED_OTHER定義優先級范圍,但自己定義的范圍一定要在minmax的返回值之間.

?

3)線程調度屬性

--競爭范圍屬性

PTHREAD_SCOPE_PROCESSPTHREAD_SCOPE_SYSTEM

個人感覺Linux中應該沒有PROCESS調度才對...由于Linux的線程和進程都是用的一種數據結構并且Linux實現的是輕量級線程.....這樣應該就僅僅有一種調度方式了吧....我在網上問了也沒人回答....

--繼承屬性

這個主要是指明新創建的線程怎樣獲得他的調度策略和相連的調度參數.

PTHREAD_INHERIT_SCHEDPTHREAD_EXPLICIT_SCHED

前者表示繼承,后者則從后面兩個屬性中設置.

?

--調度策略屬性

SCHED_FIFO

SCHED_RR

SCHED_OTHER

?

--調度參數屬性(包括優先級)

這是一個對程序猿不透明的結構體.詳細的能夠查看sched.h.可是至少包括一個sched_priority的成員.對于SCHED_FIFOSCHED_RR來說sched_priority是唯一的調度參數.

?

有以下這么些函數來獲取和設置

int?pthread_attr_getscope(const?pthread_attr_t?*restrict?attr,int?*restrict?contentionscope);

int?pthread_attr_setscope(pthread_attr_t?*attr,int?contentionscope);

?

int?pthread_attr_getinheritsched(pthread_attr_t?*attr,int?*?inherit);

int?pthread_attr_setinheritsched(pthread_attr_t?*attr,int?*inherit);

?

int?pthread_attr_getschedpolicy(pthread_attr_t?*attr,int?*policy);

int?pthread_attr_setschedpolicy(pthread_attr_t?*attr,int?*policy);

?

int?pthread_attr_getschedparam(const?pthread_attr_t?*restrict?attr,struct?sched_param?*restrict?param);

int?pthread_attr_setschedparam(const?pthread_attr_t?*restrict?attr,const?struct?sched_param*restrict?param?);

?

scope范圍,inherit繼承,policy策略,param參數(優先級)

這些函數成功都返回0.一般多數系統都不同意用戶應用隨便設置線程的調度屬性,僅僅有特權用戶才行.而且一定要創建之前設置PTHREAD_EXPLICIT_SCHED屬性.

?

4)動態改變調度策略和優先級

int?pthread_getschedparam(pthread_t?thread?,int?*restrict?policy,struct?sched_param?*restrict?param?);

int?pthread_setschedparam(pthread_t?thread?,int?policy,const?struct?sched_param?*param?);

int?pthread_setschedprio(pthread_t?thread?,int?prio);

?

這個感覺沒什么好說的.前兩個是改變策略和參數.第三個直接就是改變優先級.

當策略和優先級改變時,線程從執行狀態切換至就緒狀態,并放置到新的優先級隊列中.非常多系統對于SCHED_FIFO來說,一般不讓隨便設置成最高優先級.

?

------------------補充的切割線-----------------

我看<現代操作系統>上面說了有三種調度方式.前兩種和上面一致.最后一種叫分時.應該就是SCHED_OTHER.感覺翻譯有點問題.詳細以后看源代碼再專門寫一篇Linux調度策略的文章吧.

這里的優先級在Linux下是0~139.

當中前0~99是實時優先級.100~139則是非實時優先級.

而且Linux的時鐘是1000HZ.所以最小時間片為1ms.

對于非實時的一般就執行的時間片就比較少了.100級的時間片為800ms,139級僅僅有5ms.

這里的Linux內核版本號為2.6.

------------------幾個月后的切割線-------------------

這里的優先級好像不正確頭...Linux支持兩種優先級.一種是nice值,范圍在(-20~19)..一種是實時優先級,范圍在(0~99)

至于上面說的線程優先級可能又有不同..但Linux下線程是LWP....至少這一點存疑吧..以后看kernel源代碼再說..

----------------------------------------------------

今天又看了遍<現代操作系統>..優先級在100~139的是非實時線程的優先級..感覺分得好清楚....現代這本數的作者比Linux內核開發的大神都清楚啊...

---------------------------------------------------------------

?

?

8.線程與信號

在多線程中,信號對每一個線程都是共享的.對于每一種信號,全部線程所相應的動作都是同樣的.并且全部線程可能同一時候運行這個信號.POSIX不支持線程范圍的信號動作.

為了保證信號的一致性,統一建立全部信號的信號動作,最好用一個線程來完畢其信號的設置和改變.一定不要在可能被多線程調用的函數中改變信號的動作,由于你無法保證僅僅有一個線程調用其函數.

僅僅要信號的動作使得線程終止,暫停或者繼續,就相同會使得整個進程終止,暫停或者繼續.也就是說發送SIGKILL,SIGSTOP,SIGCONT這三種信號,都是針對進程而言的.要終止線程,能夠用cancel來取消線程.這樣就避免了整個進程由于線程的原因產生不可預測的行為.

?

多線程中,信號也分為同步和異步.同步信號由線程自己來處理.對于異步就復雜了.

異步信號假設是發送給某個線程的,那么僅僅有這個線程能收到信號.

假設是發給進程的.那么進程中全部線程都有可能收到,可是僅僅有一個未屏蔽該信號的線程來處理.詳細由哪個來運行也不確定.假設想要一個線程來接受某個異步信號,那么全部的線程都該屏蔽這個信號.

當一個信號被多個線程共享,那么這個信號句柄就得是可重入的.由于接收到信號可能讓多個線程運行這個句柄.對于PISIX指明全部函數一定要是可重入的,可是Pthread則不是全部函數都能夠重入.

當多線程共享數據時,不可避免的要用線程同步.假設想要用線程同步函數或者不可重入函數,那么最好不要用sigaction來建立句柄.能夠使用sigwait()函數來同步異步信號.

?

1)信號屏蔽

正如我上面說所,每一個線程都是有自己的屏蔽信號的.

int?pthread_sigmask(int?how,const?sigset_t?*restrict?set,sigset_t?*restrict?oset);

這個函數與sigprocmask類似,可是這個函數專門用于檢測或改變(或者兩個都有)調用線程的私有信號屏蔽.

how參數有下面幾種模式:

SIG_BLOCK:?即將set所指信號集的信號加入到當前信號屏蔽中

SIG_UNBLOCK:?set所指信號集的信號從當前信號屏蔽中去除.

SIG_SETMASK:?set所指信號集的信號設置為當前信號屏蔽中去.

一個線程的屏蔽信號,會從創建它的線程中繼承.

當信號被屏蔽的時候,假設有此信號來則會一直懸掛到被解除屏蔽或者調用了sigwait或者線程結束.

?

2)向線程發送信號

int?pthread_kill(pthread_t?thread,int?sig);

向指定線程,發送一個sig信號.sig0,kill()類似,僅僅檢查指定的線程是否存在.

?

3)等待信號

int?sigwait(const?sigset_t?*restrict?set,int?*restrict?sig);

這個函數直接從set信號集中等待信號,而且一直堵塞直到有信號來,然后直接返回,不須要設置句柄.當集合中的多個信號同一時候懸掛時,那么先返回信號數比較低的.

假設一直沒來信號集中的信號,那么會無限期的堵塞下去.這時候就能夠考慮用sigtimedwait函數了.這樣就能夠設置一個超時時間了.

?

注意用的時候還是得把其它線程中的set里的信號屏蔽掉.否則其它線程就有可能接受這個信號.

?

利用這個函數能夠方便的實現讓一個特定的線程同步等待發送給進程的異步信號.簡單來說就是異步信號處理線程.

?

?

一種新的時間通知方法:SIGEV_THREAD

?

?

?

.線程同步

這一部分主要說的就是一些鎖的運用.邊看邊回顧操作系統~

這里我第一次曉得柵欄變量用來同步.原來學操作系統中都沒有講過.網上資料也好少..按我自己的理解,柵欄同步就是設置一個閾值,然后一到閾值就同步.這本書也沒怎么寫到柵欄同步,我想應該非常easy學的,以后遇到再寫吧.

1.相互排斥變量

1)初始化與銷毀

?

pthread_mutex_t?mutex?=?PTHREAD_MUTEX_INITALIZER;

int?pthread_mutex_init(pthread_mutex_t?*restrict?mutex,const?pthread_mutexattr_t?*restrict?attr);

int?pthread_mutex_destroy(pthread_mutex_t?*mutex);

第一行的僅僅能用于靜態初始化的變量,即全局的,不能用于malloc.

?

init的第二個變量是用來指明其相互排斥變量的屬性.一般就用NULL,設置成系統默認值.而且最好僅僅初始化一次,盡管Linux下會正常返回,可是easy產生不easy發現的錯誤.初始化之后就不能改變其屬性了.詳細看以下.

?

2)相互排斥變量的屬性

屬性例如以下:

--進程共享屬性.

PTHREAD_PROCESS_PRIVATE //僅由同一個進程內的線程使用,這是默認值

PTHREAD_PROCESS_SHARED //能夠由多個進程的線程使用??注意是多個進程.一般這個效率比較低,盡量避免使用.并且在進程終止前一定要釋放,否則可能導致死鎖.并且這個相互排斥變量的存儲空間須要應用自己來分配.

?

--類型屬性值

PTHREAD_MUTEX_NORMAL //基本類型,無特定功能,最快的一種,錯誤檢查最少

PTHREAD_MUTEX_RECURSIVE //遞歸類型,能夠多次加鎖,就是信號量的意思

PTHREAD_MUTEX_ERRORCHECK //檢查并報告簡單的使用錯誤.主要用來幫助調試

PTHREAD_MUTEX_DEFAULT //這個是默認類型.Linux會映射為NORMAL類型

?

pthread_mutexattr_t的初始化什么的也要由專門的函數來取代...

int?pthread_mutexattr_init(pthread_mutexattr_t?*attr);

int?pthread_mutexattr_destroy(pthread_mutexattr_t?*attr);

這里destroy之后仍然能夠再次init.

?

int?pthread_mutexattr_setpshared(pthread_mutexattr_t?*attr,int?pshared);

int?pthread_mutexattr_getpshared(pthread_mutexattr_t?*attr,int?*restrict?pshared);

int?pthread_mutexattr_setpshared(pthread_mutexattr_t?*attr,int?type);

int?pthread_mutexattr_setpshared(pthread_mutexattr_t?*attr,int?*restrict?type);

上面兩個是用于進程共享屬性的.以下兩個用于類型屬性的.

?

3)相互排斥變量的加鎖與解鎖.

int?pthread_mutex_lock(pthread_mutex_t?*mutex);

int?pthread_mutex_trylock(pthread_mutex_t?*mutex);

int?pthread_mutex_unlock(pthread_mutex_t?*mutex);

第一個加鎖會堵塞,第二個不會.

第二個失敗會返回EBUSY.但假設鎖的屬性是PTHREAD_MUTEX_RECURSIVE的話,那么鎖計數器會加一.而且返回0.

?

鎖的操作都好復雜啊...不同屬性還有不同的處理方式...以下是補充

PTHREAD_MUTEX_NORMAL //不進行死鎖檢測.反復加鎖會導致死鎖.

PTHREAD_MUTEX_ERRORCHECK //反復加鎖或對未加鎖的mutex進行解鎖時,錯誤返回

PTHREAD_MUTEX_RECURSIVE //信號量的概念,對未鎖或者已經解鎖的解鎖,錯誤返回

PTHREAD_MUTEX_DEFAULT //Linux默認會置位NORMAL.其它系統,會導致不確定結果.

?

4)相互排斥變量與spin

這個就是自旋鎖.針對一些操作時間短的過程加鎖.這種話就比那些使用相互排斥鎖的要快一些.畢竟沒有上下文的切換,降低了系統調用的時間.

int?pthread_spin_init(pthread_spinlock_t?*lock,int?pshared):

int?pthread_spin_destroy(pthread_spinlock_t?*lock):

int?pthread_spin_lock(pthread_spinlock_t?*lock):

int?pthread_spin_trylock(pthread_spinlock_t?*lock):

int?pthread_spin_unlock(pthread_spinlock_t?*lock):

功能和相互排斥鎖全然一樣.就是線程不必堵塞直到解鎖,而是通過輪詢,不斷的查詢.所以這個鎖僅僅能用于一些加鎖過程比較短的地方.

?

2.讀寫鎖

因為相互排斥鎖和自旋鎖一定時間內僅僅同意一個線程執行,所以非常可能導致程序變成串行的.這樣就須要讀寫鎖來改善程序性能了.這個鎖主要用于讀操作頻繁但寫操作非常少的共享數據.每次能夠由多個線程讀,可是僅僅能一個線程寫.雖說提高了并行性,可是上鎖和解鎖的時間比相互排斥量開銷大.大部分時候還是盡量選擇相互排斥鎖.

一般而言,占有鎖時間比較短時使用相互排斥鎖;時間較長且讀操作多,寫操作少時才使用讀寫鎖.

?

pthread_rwlock_t?rwlock?=?PTHREAD_RWLOCK_INITALIZER

int?pthread_rwlock_init?(pthread_rwlock_t?*restrict?rwlock,const?pthread_rwlock_t?*restrict?attr);

int?pthread_rwlock_destroy(pthread_rwlock_t?*rwlock);

基本和之前的相互排斥鎖一樣.我就不多寫了.

可是讀寫鎖僅僅有共享屬性的設置,沒有類型屬性的設置.

pthread_rwlockattr_setpsharedpthread_rwlockattr_getpshared詳細的我就不多說了.

?

讀鎖

pthread_rwlock_rdlock

pthread_rwlock_tryrdlock

寫鎖

pthread_rwlock_wrlock

pthread_rwlock_trywrlock

系統一般優先考慮運行寫鎖.

?

解鎖

pthread_rwlock_unlock

這個寫和讀是統一的.

寫到這里有點疑問,就是為什么讀寫鎖不僅僅把寫的部分上鎖而讀的部分任意?

事實上讀的時候也是要避免寫的,假設之上寫鎖,非常可能會讀出錯誤結果.

?

3.條件變量

簡單來說,條件變量是為了把解鎖和等待變為原子動作所想到的一個方法.

以生產者-消費者模型來說.當產品為空時,消費者必須堵塞等待生產者生產完畢.這里可分為三個步驟:

1)釋放相互排斥變量,讓生產者可以生產產品.

2)消費者線程必須堵塞

3)當能夠消費時,解除堵塞.

?

這里有可能會讓線程永久堵塞.書上寫的我不太明確,我試著去理解下面意思.

首先,這里的消費者線程看到相互排斥量沒鎖,自己鎖上,然后進入運行步驟.再推斷是否滿足條件,假設滿足條件,就進行工作.然后釋放鎖.假設不滿足條件,那么就進入隊列,而且堵塞自己.當生產者剛好生產出來一個產品時,喚醒隊列中的線程.假設線程做完了條件推斷,剛釋放完相互排斥變量,正處于進入隊列的過程中.那么生產線程可能就不能喚醒這個消費線程(由于還沒進入隊列,但已經解鎖了.解鎖的中途生產者線程生產了產品).假設生產者在喚醒的時候隊列中沒有線程.那么這個就永久堵塞了.


這個東西主要是用來實現管程的..

?

個人覺得僅僅要有新線程進來,那么就會激活這個線程...或者僅僅要這個檢查到沒有線程等待,過一段時間再檢測就好了...只是安全起見還是用條件變量吧.


創建和銷毀條件變量

pthread_cond_t?cond?=?PTHREAD_COND_INITALIZER

int?pthread_cond_init?(pthread_cond_t?*restrict?cond,const?pthread_condattr_t?*restrict?attr);

int?pthread_cond_destroy(pthread_cond_t?*cond);

第一個是靜態初始化.第二個是動態初始化.

同之前的initattr一般為NULL,自己主動分配成系統默認的狀態.

當用init初始化時,才用destroy來銷毀條件變量.

假設你把一個正在使用的條件變量用destroy的話,會正常返回.

?

條件變量屬性

這里就僅僅有進程共享屬性.PTHREAD_PROCESS_PRIVATEPTHREAD_PROCESS_SHARED兩個.

一般這個都不用,只是還是寫一下.

int?pthread_condattr_init(pthread_condattr_t?*attr);

int?pthread_condattr_destroy(pthread_condattr_t?*attr);

int?pthread_condattr_setpthared(pthread_condattr_t?*attr,int?*pshared);

int?pthread_condattr_getpshared(pthread_condattr_t?*attr,int?*pshared);

個人感覺全然能夠寫一個函數把這個封裝起來.反正也就僅僅一種用途.

?

等待條件變量

int?pthread_cond_wait(pthread_cond_t?*restrict?cond,pthread_mutex_t?*restrict?mutex);

int?pthread_cond_timedwait(pthread_cond_t?*restrict?cond,pthread_mutex_t?*restrict?mutex,const?struct?timespec?*restrict?abstime);

兩個函數區別就是第二個函數是有時間限制的.

?

注意!線程在調用這兩個函數之前必須是鎖住的.而且在堵塞之前會自己主動釋放mutex.返回時,又一次獲得該相互排斥變量.

?

喚醒條件變量等待

int?pthread_cond_signal?(pthread_cond_t?*cond);

int?pthread_cond_broadcast(pthread_cond_t?*cond);

signal能夠至少喚醒一個在使用cond條件變量的線程.普通情況下僅僅有一個線程返回,但偶爾也可能會因為假喚醒而導致一個以上的線程被喚醒.

broadcast就是廣播的意思,所以這個是喚醒全部在cond上的線程.喚醒順序肯定不是同一時候的.這個取決于線程的調度策略和優先級,也取決于他們又一次獲得相連相互排斥變量時的競爭順序.

?

這里提到了信號.事實上對于信號來說,發送是以進程為單位的.所以信號怎樣影響線程是一個須要細致學習的事情,我在后面會試著具體寫一些.

?

4.柵欄(屏障)

又看新書了..所以剛好有這個內容..原來大部分人都叫屏障...就我看的書叫柵欄...只是網上內容也非常少..基本用條件變量和相互排斥變量搭配就能夠實現.所以我就不多說了.僅僅寫下幾個函數.

int?pthread_barrier_init(pthread_barrier_t?*restrict?barrier,?const?pthread_barrierattr_t?*restrict?attr,?unsigned?count);

int?pthread_barrier_wait(pthread_barrier_t?*barrier);

int?pthread_barrier_destroy(pthread_barrier_t?*barrier);

簡單來說,init中的count決定等待線程的個數.wait表示這個線程準備就緒,也就是count+1.然后destroy就是解除柵欄.

?

細節因為文章不多,我也不多寫了.詳細用到在man.

?

.操作線程專有數據

除開全局變量和局部變量外,線程還能夠擁有線程專屬的數據,局部變量也算是私有數據了.感覺假設自己編寫的時候注意一點,全然能夠就用全局變量,感覺非常雞肋...

當中還設置了構造和析構函數,于是幫助線程來分配私有空間和釋放私有空間的作用..也就是C++中的構造和析構函數了...

?

線程專有數據是通過鍵-值的方式相應的.非常像C++里面的map.

?

創建和刪除:

int?pthread_key_create(pthread_key_t??*key,void?(*destructor)(void?*));

這個函數創建一個鍵值,通過key參數返回.這里還沒有分配值空間,之后還要調用pthread_getspecific來分配空間.每一個鍵值僅僅能創建一次,所以不要用同一個key地址來多次創建.

第二個參數則是一個析構函數指針.一定要指向一個析構函數,不然就內存泄漏了.線程結束的時候會自己主動調用這個線程的.

?

為了避免不同鍵值被同一變量多次賦值.能夠把創建函數封裝在以下的函數的第二個參數來保證線程不會反復創建.

pthread_once_t?once_control?=?PTHREAD_ONCE_INIT

int?pthread_once(pthread_once_t?*once_control?,void?(*init_routine)(void?));

當中第一排的變量最好是一個全局變量,當然你用const定義也能夠,但一定要初始化.

以下函數的第一個參數就是第一排定義的,保證僅僅被初始化一次.,第二個則是初始化函數.而且這個函數沒有參數.但要注意,這個指針要指向一個初始化函數,這個初始化函數里面包括pthread_key_create這個函數.

我來舉個樣例:

void?key_once_init(){

int?rv;

   ......

   rv=pthread_key_create(&key(全局變量),NULL);

   .....

}

這樣就設置好了初始化函數.key盡管設置的是全局變量,可是每一個線程來取出的值是不同的...感覺好雞肋....僅僅是換個方式來保證不內存泄漏罷了...

而且這個函數還能夠用來初始化全部動態初始化的對象.比方相互排斥變量,條件變量等..

?

int?pthread_key_delete(pthread_key_t?key);

這個就不用多說了.刪除鍵.當然,這里要保證后面全部操作都不會再使用這個鍵才行.

?

注意,鍵值不是隨意多個的.<limits.h>中包括一個宏PTHREAD_KEYS_MAX表示一個線程最多有多少個鍵值.

?

?

賦值:

int?pthread_setspecific(pthread_key_t?key?,const?void?*value);

void?*pthread_getspecific(pthread_key_t?key);

第一個函數是設置,第二個是獲取值.

第一個函數的第二個參數,為了能分配各種類型的數據結構,通常是一個malloc返回的地址.

getspecifickey假設并沒有之前設置setspecific的話,那么返回則是不確定的.就像未初始化的變量一樣.

?

?

通過時鐘來進行時間通知:

Linux時間相關的文章中寫了這個方式.就是通過定時器的方式

int?timer_create(clockid_t?clockid,struct?sigevent?*restrict?evp,timer_t?*restrict?timerid);

也就是在第二個參數,struct?sigevent中設置其成員:

int?sigev_notify???通知類型

int?sigev_signo????信號類型

union?signval?sigev_value????信號參數值

void?(*)(union?sigval)??sigev_notify_function???通知函數

(pthread_attr_t??*)??sigev_notify_attributes????通知屬性

sigev_notify能夠取值為

SIGEV_NONE????不生成異步信號

SIGEV_SIGNAL??生成排隊的信號,并隨信號攜帶一個應用定義的值,由于是排隊的,所以一定是實時信號,或者說可靠信號.

SIGEV_THREAD??運行一個通知函數,這個是線程里面的高級使用方法,在線程那篇我會補充上的.

.

這里說的就是用SIGEV_THREAD這個宏.假設對sigev_notify設置了SIGEV_THREAD之后,那么就會使用西面兩個成員.一個是線程的運行函數,一個是線程的屬性.假設這么create之后,其異步事件來的時候,會創建一個新線程,屬性為第二參數的sigev_notify_attributes,運行開始地址為sigev_notify_function.給線程開始函數的參數就是sigev_value.

?

而且在這個時候,sigev_signo能夠不用寫.這也是為什么我在上面寫”異步事件”的原因





本文轉自mfrbuaa博客園博客,原文鏈接:http://www.cnblogs.com/mfrbuaa/p/5315046.html,如需轉載請自行聯系原作者

總結

以上是生活随笔為你收集整理的Linux编程---线程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。