Linux编程---线程
首先說一下線程的概念.事實上就是運行在進程的上下文環(huán)境中的一個運行流.普通進程僅僅有一條運行流,可是線程提供了多種運行的路徑并行的局面.
同一時候,線程還分為核心級線程和用戶級線程.主要差別在屬于核內還是核外.
核心級線程,地位基本和進程相當,由內核調度.也就是說這樣的系統(tǒng)時間片是按線程來分配的.這樣的線程的優(yōu)點就是能夠適當?shù)倪\用SMP,即針對多核CPU進行調度.
用戶級線程,在用戶態(tài)來調度.所以相對來說,切換的調度時間相對核心級線程來說要快不少.可是不能針對SMP進行調度.
?
對于如今的系統(tǒng)來說,純粹的用戶級線程僅僅存在于實驗室中吧.
對于Linux中實現(xiàn)的線程來說,支持LWP(輕量級進程).把用戶級線程映射到內核級上.簡單來說,Linux僅僅有內核級進程,不存在真正的線程.可是我們也能夠把LWP叫做線程.
?
線程和進程對操作系統(tǒng)來說相當于是平行的關系,但大部分資源是隸屬于進程的.假設進程掛了,所有線程也會終止.(直接賦值其進程的頁表,然后改動特殊的部分就可以創(chuàng)建一下線程~復習一下虛擬存儲~)
?
順帶一提erlang虛擬機下的輕量級線程是虛擬機下實現(xiàn)的。應該叫進程。.我記得在哪看過,說erlang切換線程比c運行的線程切換還快.應該能夠分類成用戶級線程吧。
而Linux僅僅有LWP..
?
這里我還有個疑問,就是Linux的線程是否是環(huán)保線程.這個概念也是今天剛看<現(xiàn)代操作系統(tǒng)>中提到的.意思就是對于進程來說,線程并不馬上釋放資源,而是等到進程結束再釋放.這樣就省去了線程又一次生成的開銷.對于server來說應該是非常實用的一個策略呢.有知道的嗎?
?--------------------補充說明切割線-----------------------
又看了些書.發(fā)現(xiàn)實際上linux的線程創(chuàng)建過程主要調用的是clone函數(shù).
這個函數(shù)的第二個參數(shù)有好幾種狀態(tài)選擇.這些選擇決定了clone出來的進程是一般所說的線程還是一個進程.
而且有下面幾種標志能夠選擇:
CLONE_VM 置1:創(chuàng)建線程--共享地址空間???置0:創(chuàng)建進程,不共享地址空間,但會復制
CLONE_FS 共享umask?不共享
CLONE_FILES 共享文件描寫敘述符 拷貝文件描寫敘述符
CLONE_SIGHAND 共享信號句柄表 賦值信號句柄表
CLONE_PID 新線程獲得舊的PID?新線程獲得自己的PID
CLONE_PARENT 新線程與調度這有同樣的父親 新線程的父親是調用者
Linux對進程標識符PID和任務標識符TID進行了區(qū)分!!而且兩個都在task_struct結構中.
當用clone函數(shù)創(chuàng)建一個新進程而不須要和舊進程共享不論什么信息時,PID被設置成一個新值(新進程?fork?
).否則任務得到一個新的任務標識符,可是PID不變.
TID也就是我后面會說的線程標識符.
預計pthread庫中,應該就是把這些標志都選上,然后創(chuàng)建的.
----------------------------------------------------------------
?
使用線程的程序一般具有一下特征:
1.可以與其它任務并行運行.
2.有可能會被堵塞較長時間,但這時候其它進程可并發(fā)運行.
3.須要回應異步事件.畢竟異步本身就是不確定堵塞時間的.
4.線程使用CPU的時間足夠長.不然切換的代價也不少.
?
這里要寫的是Pthread線程.也就是POSIX定義的線程接口.這個接口包括100多個函數(shù),全部函數(shù)名都已pthread_開頭.功能上大致分為三類:
線程管理:?這類函數(shù)負責線程的創(chuàng)建,終止,匯合,取消,以及線程屬性的設置和查詢等.
線程同步:?Pthread提供了相互排斥變量,條件變量,柵欄(屏障)變量等手段支持線程間的同步.
操作線程專有數(shù)據:?多線程程序中,全局數(shù)據分為全部線程都能夠訪問的共享數(shù)據和單個線程內全部函數(shù)都能夠訪問的線程專有數(shù)據.
?
這里要注意一點:差點兒全部函數(shù)都部通過errno來報錯.運行成功均返回0.除開pthread_getspecific全然不報錯之外,其余的返回錯誤號.
可是對于單獨的一個線程,報錯的時候改動你的errno,其它線程是無法干擾的.實際上Linux的errno是一個局部變量(這里也是網上查的,只是都是非常老的帖子里面的,有錯誤請指正)
?
線程標識:
pthread_t?pthread_self(void);???用于獲得線程ID,即TID
pthread_equal(pthread_t?t1,pthread_t?t2);???用于比較兩個線程標號,pthread_t可能并非整形.
?
?
一.線程管理
1.創(chuàng)建線程
int?pthread_create(pthread_t?*restrict?thread,const?pthread_attr_t?*restrict?attr,void?*(*start_routine)(void*),void?*?restrict?arg);
第一個參數(shù)傳回其線程的TID,第二個參數(shù)用來設置線程的屬性,一般寫NULL,第三個為線程開始運行的函數(shù)指針,第四個參數(shù)為傳遞給線程的唯一一個參數(shù).
?
注意Pthread的返回值是統(tǒng)一的.之后都不會再說了.
?
2.終止線程
int?pthread_exit(void?*value_ptr);
這個函數(shù)是用于線程自己終止自己的.當中的參數(shù)相當于是返回值,當還有一個線程調用pthread_join()等待其結束時,這個值會給pthread_join中的參數(shù).
這里須要注意的就是,不要讓value_ptr指向局部變量,由于線程結束,其資源會被回收.最好養(yǎng)成用malloc直接申請一個的習慣.免得在線程分離的情況下出現(xiàn)故障.(假設Linux是環(huán)保線程的話,那么這個就不用操心太多了.)
同一時候,線程的回收并不回收不論什么進程相關的資源.
?
假設你用exit()的話,那么整個進程都會被回收..而不是回收線程.而且注意,假設main線程結束了的話,那么線程也不運行了!!!假設你想讓線程運行完再結束進程,一定要設置好同步!!pthread_join就非常好.
?
3.等待線程終止
int?pthread_join(pthread_t?thread,void?**value_ptr);
第一個參數(shù)是指定等待線程的TID.第二個則是調用pthread_exit返回的參數(shù).
而且這個函數(shù)是會堵塞的!這個函數(shù)類似進程中的wait函數(shù),還具有釋放資源的功能.這還涉及到一個可匯合與分離的線程概念.
可匯合的意思就是說,線程的資源在返回給pthread_join之后才釋放.
分離的意思就是資源的釋放有系統(tǒng)來搞定.
這樣做的目的是盡可能的節(jié)省系統(tǒng)資源,提高效率.可能一般小程序體現(xiàn)不出什么,可是對于server程序來說,一點性能的提升就能干非常多事~
?
這里的join僅僅能用于可匯合的線程,分離線程則不行.
默認的線程創(chuàng)建出來的都是可匯合的.
?
另一點,線程是能夠互相等待的.并非僅僅有主線程才干等待其它線程.不論什么線程都能夠等待另外一個線程的結束.
?
4.分離線程
int?pthread_detach(pthread_t?thread);
指定一個TID號,然后這個線程就分離了.so?easy...
當然,你得細致考慮這個線程是否須要返回才是.
假設調用了兩次的話,那么Linux下會返回EINVAL.
分離之后線程就不由父線程管了.pthread_join就不會等待它了.
?
注意,分離線程的TID在線程終止后能夠馬上又一次分配給其它創(chuàng)建的線程.
當應用程序須要與分離的線程同步,應當現(xiàn)判別該線程是否已經終止.能夠用全局變量來當作標志.避免分離的進程隨主線程的exit而終止.
?
5.創(chuàng)建特殊的線程
這個是補充之前的創(chuàng)建線程的.
這里用到了非常多函數(shù)...我就不明確為什么不直接用結構體賦值取代...非要搞函數(shù)..
1)線程屬性對象的初始化和銷毀函數(shù)
int?pthread_attr_init(pthread_attr_t?*attr)
int?pthread_attr_destroy(pthread_attr_t?*attr)
初始化包括兩件事:分配空間,賦初始值.所以非常明顯destroy是用來釋放空間的.
注意這里傳值傳的是一個指針,而不是結構體!
?
2)線程分離狀態(tài)的查詢與設置函數(shù)
int?pthread_attr_getdetachstate(pthread_attr_t?*attr,int?*detachstate);
int?pthread_attr_setdetachstate(pthread_attr_t?*attr,int?*detachstate);
get返回之后,設置第二個參數(shù)為PTHREAD_CREATE_DETACHED或者PTHREAD_CREATE_JOINABLE.意思非常明顯了.
set則能夠通過第二個參數(shù)設置.選項就是上面兩個.
?
3)線程棧的查詢和設置函數(shù)
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);
第二個參數(shù)同上,一個取值,一個設置.而且設置的時候值不能小于PTHREAD_STACK_MIN
這里書上沒有說,可是我查的是按字節(jié)來分配的.通常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);
這個就是獲得和設置棧的虛擬地址沒多少可說的...
這里要注意的就是:
--對于多個線程,不要用統(tǒng)一的屬性來設置地址.畢竟虛擬地址空間是統(tǒng)一的.
--這個線程應當是可匯合的.(這里我不太明確為什么應該這樣,詳細看源代碼再說吧)
--這個線程棧的保護區(qū)應當是由自己設置的.
--內存對齊.
?
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)棧一處保護區(qū)大小的查詢和設置函數(shù)
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);
棧保護區(qū)是在線程棧之后的一片區(qū)域,而且設置了特殊處理假設產生棧溢出的話,那么線程就會收到一個SIGSEGV信號.
通常有兩種情況須要調整其大小:
--節(jié)省存儲空間.對于嵌入式設備比較實用吧.
--線程須要更大的空間存放局部變量時.比方用到了深度遞歸程序的話~(預計線程的進入函數(shù)也能遞歸~)
?
6.取消線程
與之前的線程用exit終止自己不一樣,這個是一個線程來取消另外一個線程.對于一些并行編程應該非常有作用吧.比方你用BFS搜索一個解空間,每一個線程分配一個搜索范圍.搜索到答案之后,其余在搜索解的線程就能夠取消掉了.
同一時候,這個取消是”友好的”,意思就是并非強行讓線程終止.就和IO差點兒相同.有一個緩存的過程.自己能夠去查看是否緩存好,也能夠通過異步來實現(xiàn).
?
可取消的屬性
--可取消狀態(tài):這個表示能否夠被其它線程取消.默認屬性是同意的.
--可取消類型:默認是延遲取消.也就是線程自己檢查是否可被取消.這就類似同步IO,必須自己去檢查是否有數(shù)據來.為了效率還有第二種異步類型.
?
假設要改變默認值,能夠用以下兩個函數(shù):
int?pthread_setcancelstate?(int?state,int?*oldstate);
int?pthread_setcanceltype?(int?tyep,int?*oldtype);
第一個參數(shù)都是新的設置,第二個則是原來的設置值.
state能夠取值為PTHREAD_CANCEL_ENABLE和PTHREAD_CANCEL_DISABLE
type能夠取值為PTHREAD_CANCEL_DEFERRED和PTHREAD_CANCEL_ASYNCHRONOUS.
注意,假設對一個不同意取消的線程發(fā)送一個請求,那么請求會保持.這一點和信號不同.
?
明白了屬性,以下再來介紹實際操作.
int?pthread_cancel(pthread_t?target_thread);
這個是發(fā)送取消請求.參數(shù)是線程ID.而且不等待目標線程的終止.
?
延遲類型下,分散在非常多地方..比方各種系統(tǒng)調用和phread函數(shù)中都會檢查的.這些個函數(shù)太多了,我就不細寫了....詳細能夠查手冊.只是能夠知道的是,這些函數(shù)中多數(shù)為慢系統(tǒng)調用(有可能被無限堵塞).
?
延遲類型還能夠用專門的函數(shù)來檢查:
void?pthread_testcancel();
檢查到了之后直接取消線程.
?
異步類型就是隨便什么時候終止都行了.這就必需要需要保證相互排斥量,線程專有數(shù)據,堆空間什么的沒在使用了.
當然,為了異步編程方便,實際上在每一個線程中有一個隱含的清理器棧,就是個函數(shù)指針棧.每次取消線程或者退出線程的時候會自己主動運行這些棧中的函數(shù).在必要時,也能夠自己彈出棧,然后運行.
void?pthread_cleanup_push(vodi?(*routine?)(void?*),void?*arg);
void?pthread_cleanup_pop(int?execute);
第一個函數(shù)是入棧.其第二個參數(shù)是在第一個參數(shù)運行時,送給第一個參數(shù)的的參數(shù)(好繞)....
第二個函數(shù)則是從其棧中彈出.當參數(shù)不是0的時候,會運行!
?
感覺這兩個函數(shù)還蠻方便的.比析構好用多了.
?
7.線程調度?
1)線程調度范圍
POSIX定義了兩種調度模式:進程調度模式和系統(tǒng)調度模式.前者的競爭范圍為進程內,后者則為進程間.感覺就是所謂的用戶級線程和內核級線程.但Linux僅僅有系統(tǒng)調度模式.在APUE也沒有相關的內容.以后做了實驗再來補一篇博客吧.
PTHREAD_SCOPE_PROCESS表示進程調度模式.
PTHREAD_SCOPE_SYSTEM?表示系統(tǒng)調度模式.
這么分,主要是為了區(qū)分線程競爭的對手.前者僅僅在進程內區(qū)分.后者則和其它進程一起競爭.
主要在phread_attr_t類型變量中設置后,傳遞給pthread_create.
?
2)線程調度策略與優(yōu)先級
SCHED_FIFO?基于優(yōu)先級的先進先出調度策略.不同優(yōu)先級不同隊列.這樣的策略可能讓高優(yōu)先級線程獨占系統(tǒng).
SCHED_RR?循環(huán)調度.也就是時間片輪轉.也有FIFO的優(yōu)先級隊列.盡管是時間片輪轉,但仍有可能獨占系統(tǒng).
SCHED_OTHER?這樣的一般類UNIX系統(tǒng)默覺得對進城的系統(tǒng)分時調度策略.這隨系統(tǒng)不同,策略也不同,所以不可移植.當然,也能夠採用自定義的策略.
?
int?sched_get_priority_max(int?policy);
int?sched_get_priority_min(int?policy);
兩個函數(shù)能夠用來得到優(yōu)先級的最大值和最小值.都是個int類型.
參數(shù)的意思就是上面所說的策略.把宏定義填進去就能夠了.
?
POSIX沒有對SCHED_OTHER定義優(yōu)先級范圍,但自己定義的范圍一定要在min到max的返回值之間.
?
3)線程調度屬性
--競爭范圍屬性
PTHREAD_SCOPE_PROCESS和PTHREAD_SCOPE_SYSTEM
個人感覺Linux中應該沒有PROCESS調度才對...由于Linux的線程和進程都是用的一種數(shù)據結構并且Linux實現(xiàn)的是輕量級線程.....這樣應該就僅僅有一種調度方式了吧....我在網上問了也沒人回答....
--繼承屬性
這個主要是指明新創(chuàng)建的線程怎樣獲得他的調度策略和相連的調度參數(shù).
PTHREAD_INHERIT_SCHED和PTHREAD_EXPLICIT_SCHED
前者表示繼承,后者則從后面兩個屬性中設置.
?
--調度策略屬性
SCHED_FIFO
SCHED_RR
SCHED_OTHER
?
--調度參數(shù)屬性(包括優(yōu)先級)
這是一個對程序猿不透明的結構體.詳細的能夠查看sched.h.可是至少包括一個sched_priority的成員.對于SCHED_FIFO和SCHED_RR來說sched_priority是唯一的調度參數(shù).
?
有以下這么些函數(shù)來獲取和設置
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參數(shù)(優(yōu)先級)
這些函數(shù)成功都返回0.一般多數(shù)系統(tǒng)都不同意用戶應用隨便設置線程的調度屬性,僅僅有特權用戶才行.而且一定要創(chuàng)建之前設置PTHREAD_EXPLICIT_SCHED屬性.
?
4)動態(tài)改變調度策略和優(yōu)先級
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);
?
這個感覺沒什么好說的.前兩個是改變策略和參數(shù).第三個直接就是改變優(yōu)先級.
當策略和優(yōu)先級改變時,線程從執(zhí)行狀態(tài)切換至就緒狀態(tài),并放置到新的優(yōu)先級隊列中.非常多系統(tǒng)對于SCHED_FIFO來說,一般不讓隨便設置成最高優(yōu)先級.
?
------------------補充的切割線-----------------
我看<現(xiàn)代操作系統(tǒng)>上面說了有三種調度方式.前兩種和上面一致.最后一種叫分時.應該就是SCHED_OTHER吧.感覺翻譯有點問題.詳細以后看源代碼再專門寫一篇Linux調度策略的文章吧.
這里的優(yōu)先級在Linux下是0~139.
當中前0~99是實時優(yōu)先級.100~139則是非實時優(yōu)先級.
而且Linux的時鐘是1000HZ的.所以最小時間片為1ms.
對于非實時的一般就執(zhí)行的時間片就比較少了.100級的時間片為800ms,而139級僅僅有5ms.
這里的Linux內核版本號為2.6.
------------------幾個月后的切割線-------------------
這里的優(yōu)先級好像不正確頭...Linux支持兩種優(yōu)先級.一種是nice值,范圍在(-20~19)..一種是實時優(yōu)先級,范圍在(0~99)
至于上面說的線程優(yōu)先級可能又有不同..但Linux下線程是LWP....至少這一點存疑吧..以后看kernel源代碼再說..
----------------------------------------------------
今天又看了遍<現(xiàn)代操作系統(tǒng)>..優(yōu)先級在100~139的是非實時線程的優(yōu)先級..感覺分得好清楚....現(xiàn)代這本數(shù)的作者比Linux內核開發(fā)的大神都清楚啊...
---------------------------------------------------------------
?
?
8.線程與信號
在多線程中,信號對每一個線程都是共享的.對于每一種信號,全部線程所相應的動作都是同樣的.并且全部線程可能同一時候運行這個信號.即POSIX不支持線程范圍的信號動作.
為了保證信號的一致性,統(tǒng)一建立全部信號的信號動作,最好用一個線程來完畢其信號的設置和改變.一定不要在可能被多線程調用的函數(shù)中改變信號的動作,由于你無法保證僅僅有一個線程調用其函數(shù).
僅僅要信號的動作使得線程終止,暫?;蛘呃^續(xù),就相同會使得整個進程終止,暫停或者繼續(xù).也就是說發(fā)送SIGKILL,SIGSTOP,SIGCONT這三種信號,都是針對進程而言的.要終止線程,能夠用cancel來取消線程.這樣就避免了整個進程由于線程的原因產生不可預測的行為.
?
多線程中,信號也分為同步和異步.同步信號由線程自己來處理.對于異步就復雜了.
異步信號假設是發(fā)送給某個線程的,那么僅僅有這個線程能收到信號.
假設是發(fā)給進程的.那么進程中全部線程都有可能收到,可是僅僅有一個未屏蔽該信號的線程來處理.詳細由哪個來運行也不確定.假設想要一個線程來接受某個異步信號,那么全部的線程都該屏蔽這個信號.
當一個信號被多個線程共享,那么這個信號句柄就得是可重入的.由于接收到信號可能讓多個線程運行這個句柄.對于PISIX指明全部函數(shù)一定要是可重入的,可是Pthread則不是全部函數(shù)都能夠重入.
當多線程共享數(shù)據時,不可避免的要用線程同步.假設想要用線程同步函數(shù)或者不可重入函數(shù),那么最好不要用sigaction來建立句柄.能夠使用sigwait()函數(shù)來同步異步信號.
?
1)信號屏蔽
正如我上面說所,每一個線程都是有自己的屏蔽信號的.
int?pthread_sigmask(int?how,const?sigset_t?*restrict?set,sigset_t?*restrict?oset);
這個函數(shù)與sigprocmask類似,可是這個函數(shù)專門用于檢測或改變(或者兩個都有)調用線程的私有信號屏蔽.
how參數(shù)有下面幾種模式:
SIG_BLOCK:?即將set所指信號集的信號加入到當前信號屏蔽中
SIG_UNBLOCK:?將set所指信號集的信號從當前信號屏蔽中去除.
SIG_SETMASK:?將set所指信號集的信號設置為當前信號屏蔽中去.
一個線程的屏蔽信號,會從創(chuàng)建它的線程中繼承.
當信號被屏蔽的時候,假設有此信號來則會一直懸掛到被解除屏蔽或者調用了sigwait或者線程結束.
?
2)向線程發(fā)送信號
int?pthread_kill(pthread_t?thread,int?sig);
向指定線程,發(fā)送一個sig信號.當sig為0時,和kill()類似,僅僅檢查指定的線程是否存在.
?
3)等待信號
int?sigwait(const?sigset_t?*restrict?set,int?*restrict?sig);
這個函數(shù)直接從set信號集中等待信號,而且一直堵塞直到有信號來,然后直接返回,不須要設置句柄.當集合中的多個信號同一時候懸掛時,那么先返回信號數(shù)比較低的.
假設一直沒來信號集中的信號,那么會無限期的堵塞下去.這時候就能夠考慮用sigtimedwait函數(shù)了.這樣就能夠設置一個超時時間了.
?
注意用的時候還是得把其它線程中的set里的信號屏蔽掉.否則其它線程就有可能接受這個信號.
?
利用這個函數(shù)能夠方便的實現(xiàn)讓一個特定的線程同步等待發(fā)送給進程的異步信號.簡單來說就是異步信號處理線程.
?
?
一種新的時間通知方法:SIGEV_THREAD
?
?
?
二.線程同步
這一部分主要說的就是一些鎖的運用.邊看邊回顧操作系統(tǒng)~
這里我第一次曉得柵欄變量用來同步.原來學操作系統(tǒng)中都沒有講過.網上資料也好少..按我自己的理解,柵欄同步就是設置一個閾值,然后一到閾值就同步.這本書也沒怎么寫到柵欄同步,我想應該非常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);
第一行的僅僅能用于靜態(tài)初始化的變量,即全局的,不能用于malloc的.
?
init的第二個變量是用來指明其相互排斥變量的屬性.一般就用NULL,設置成系統(tǒng)默認值.而且最好僅僅初始化一次,盡管Linux下會正常返回,可是easy產生不easy發(fā)現(xiàn)的錯誤.初始化之后就不能改變其屬性了.詳細看以下.
?
2)相互排斥變量的屬性
屬性例如以下:
--進程共享屬性.
PTHREAD_PROCESS_PRIVATE //僅由同一個進程內的線程使用,這是默認值
PTHREAD_PROCESS_SHARED //能夠由多個進程的線程使用??注意是多個進程.一般這個效率比較低,盡量避免使用.并且在進程終止前一定要釋放,否則可能導致死鎖.并且這個相互排斥變量的存儲空間須要應用自己來分配.
?
--類型屬性值
PTHREAD_MUTEX_NORMAL //基本類型,無特定功能,最快的一種,錯誤檢查最少
PTHREAD_MUTEX_RECURSIVE //遞歸類型,能夠多次加鎖,就是信號量的意思
PTHREAD_MUTEX_ERRORCHECK //檢查并報告簡單的使用錯誤.主要用來幫助調試
PTHREAD_MUTEX_DEFAULT //這個是默認類型.Linux會映射為NORMAL類型
?
pthread_mutexattr_t的初始化什么的也要由專門的函數(shù)來取代...
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的話,那么鎖計數(shù)器會加一.而且返回0.
?
鎖的操作都好復雜啊...不同屬性還有不同的處理方式...以下是補充
PTHREAD_MUTEX_NORMAL //不進行死鎖檢測.反復加鎖會導致死鎖.
PTHREAD_MUTEX_ERRORCHECK //反復加鎖或對未加鎖的mutex進行解鎖時,錯誤返回
PTHREAD_MUTEX_RECURSIVE //信號量的概念,對未鎖或者已經解鎖的解鎖,錯誤返回
PTHREAD_MUTEX_DEFAULT //Linux默認會置位NORMAL.其它系統(tǒng),會導致不確定結果.
?
4)相互排斥變量與spin鎖
這個就是自旋鎖.針對一些操作時間短的過程加鎖.這種話就比那些使用相互排斥鎖的要快一些.畢竟沒有上下文的切換,降低了系統(tǒng)調用的時間.
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.讀寫鎖
因為相互排斥鎖和自旋鎖一定時間內僅僅同意一個線程執(zhí)行,所以非常可能導致程序變成串行的.這樣就須要讀寫鎖來改善程序性能了.這個鎖主要用于讀操作頻繁但寫操作非常少的共享數(shù)據.每次能夠由多個線程讀,可是僅僅能一個線程寫.雖說提高了并行性,可是上鎖和解鎖的時間比相互排斥量開銷大.大部分時候還是盡量選擇相互排斥鎖.
一般而言,占有鎖時間比較短時使用相互排斥鎖;時間較長且讀操作多,寫操作少時才使用讀寫鎖.
?
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_setpshared和pthread_rwlockattr_getpshared詳細的我就不多說了.
?
讀鎖
pthread_rwlock_rdlock
pthread_rwlock_tryrdlock
寫鎖
pthread_rwlock_wrlock
pthread_rwlock_trywrlock
系統(tǒng)一般優(yōu)先考慮運行寫鎖.
?
解鎖
pthread_rwlock_unlock
這個寫和讀是統(tǒng)一的.
寫到這里有點疑問,就是為什么讀寫鎖不僅僅把寫的部分上鎖而讀的部分任意?
事實上讀的時候也是要避免寫的,假設之上寫鎖,非常可能會讀出錯誤結果.
?
3.條件變量
簡單來說,條件變量是為了把解鎖和等待變?yōu)樵觿幼魉氲降囊粋€方法.
以生產者-消費者模型來說.當產品為空時,消費者必須堵塞等待生產者生產完畢.這里可分為三個步驟:
1)釋放相互排斥變量,讓生產者可以生產產品.
2)消費者線程必須堵塞
3)當能夠消費時,解除堵塞.
?
這里有可能會讓線程永久堵塞.書上寫的我不太明確,我試著去理解下面意思.
首先,這里的消費者線程看到相互排斥量沒鎖,自己鎖上,然后進入運行步驟.再推斷是否滿足條件,假設滿足條件,就進行工作.然后釋放鎖.假設不滿足條件,那么就進入隊列,而且堵塞自己.當生產者剛好生產出來一個產品時,喚醒隊列中的線程.假設線程做完了條件推斷,剛釋放完相互排斥變量,正處于進入隊列的過程中.那么生產線程可能就不能喚醒這個消費線程(由于還沒進入隊列,但已經解鎖了.解鎖的中途生產者線程生產了產品).假設生產者在喚醒的時候隊列中沒有線程.那么這個就永久堵塞了.
這個東西主要是用來實現(xiàn)管程的..
?
個人覺得僅僅要有新線程進來,那么就會激活這個線程...或者僅僅要這個檢查到沒有線程等待,過一段時間再檢測就好了...只是安全起見還是用條件變量吧.
創(chuàng)建和銷毀條件變量
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);
第一個是靜態(tài)初始化.第二個是動態(tài)初始化.
同之前的init的attr一般為NULL,自己主動分配成系統(tǒng)默認的狀態(tài).
當用init初始化時,才用destroy來銷毀條件變量.
假設你把一個正在使用的條件變量用destroy的話,會正常返回.
?
條件變量屬性
這里就僅僅有進程共享屬性.PTHREAD_PROCESS_PRIVATE和PTHREAD_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);
個人感覺全然能夠寫一個函數(shù)把這個封裝起來.反正也就僅僅一種用途.
?
等待條件變量
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);
兩個函數(shù)區(qū)別就是第二個函數(shù)是有時間限制的.
?
注意!線程在調用這兩個函數(shù)之前必須是鎖住的.而且在堵塞之前會自己主動釋放mutex.返回時,又一次獲得該相互排斥變量.
?
喚醒條件變量等待
int?pthread_cond_signal?(pthread_cond_t?*cond);
int?pthread_cond_broadcast(pthread_cond_t?*cond);
signal能夠至少喚醒一個在使用cond條件變量的線程.普通情況下僅僅有一個線程返回,但偶爾也可能會因為假喚醒而導致一個以上的線程被喚醒.
broadcast就是廣播的意思,所以這個是喚醒全部在cond上的線程.喚醒順序肯定不是同一時候的.這個取決于線程的調度策略和優(yōu)先級,也取決于他們又一次獲得相連相互排斥變量時的競爭順序.
?
這里提到了信號.事實上對于信號來說,發(fā)送是以進程為單位的.所以信號怎樣影響線程是一個須要細致學習的事情,我在后面會試著具體寫一些.
?
4.柵欄(屏障)
又看新書了..所以剛好有這個內容..原來大部分人都叫屏障...就我看的書叫柵欄...只是網上內容也非常少..基本用條件變量和相互排斥變量搭配就能夠實現(xiàn).所以我就不多說了.僅僅寫下幾個函數(shù).
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決定等待線程的個數(shù).wait表示這個線程準備就緒,也就是count+1.然后destroy就是解除柵欄.
?
細節(jié)因為文章不多,我也不多寫了.詳細用到在man吧.
?
三.操作線程專有數(shù)據
除開全局變量和局部變量外,線程還能夠擁有線程專屬的數(shù)據,局部變量也算是私有數(shù)據了.感覺假設自己編寫的時候注意一點,全然能夠就用全局變量,感覺非常雞肋...
當中還設置了構造和析構函數(shù),于是幫助線程來分配私有空間和釋放私有空間的作用..也就是C++中的構造和析構函數(shù)了...
?
線程專有數(shù)據是通過鍵-值的方式相應的.非常像C++里面的map.
?
創(chuàng)建和刪除:
int?pthread_key_create(pthread_key_t??*key,void?(*destructor)(void?*));
這個函數(shù)創(chuàng)建一個鍵值,通過key參數(shù)返回.這里還沒有分配值空間,之后還要調用pthread_getspecific來分配空間.每一個鍵值僅僅能創(chuàng)建一次,所以不要用同一個key地址來多次創(chuàng)建.
第二個參數(shù)則是一個析構函數(shù)指針.一定要指向一個析構函數(shù),不然就內存泄漏了.線程結束的時候會自己主動調用這個線程的.
?
為了避免不同鍵值被同一變量多次賦值.能夠把創(chuàng)建函數(shù)封裝在以下的函數(shù)的第二個參數(shù)來保證線程不會反復創(chuàng)建.
pthread_once_t?once_control?=?PTHREAD_ONCE_INIT
int?pthread_once(pthread_once_t?*once_control?,void?(*init_routine)(void?));
當中第一排的變量最好是一個全局變量,當然你用const定義也能夠,但一定要初始化.
以下函數(shù)的第一個參數(shù)就是第一排定義的,保證僅僅被初始化一次.,第二個則是初始化函數(shù).而且這個函數(shù)沒有參數(shù).但要注意,這個指針要指向一個初始化函數(shù),這個初始化函數(shù)里面包括pthread_key_create這個函數(shù).
我來舉個樣例:
void?key_once_init(){
int?rv;
......
rv=pthread_key_create(&key(全局變量),NULL);
.....
}
這樣就設置好了初始化函數(shù).key盡管設置的是全局變量,可是每一個線程來取出的值是不同的...感覺好雞肋....僅僅是換個方式來保證不內存泄漏罷了...
而且這個函數(shù)還能夠用來初始化全部動態(tài)初始化的對象.比方相互排斥變量,條件變量等..
?
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);
第一個函數(shù)是設置,第二個是獲取值.
第一個函數(shù)的第二個參數(shù),為了能分配各種類型的數(shù)據結構,通常是一個malloc返回的地址.
getspecific的key假設并沒有之前設置setspecific的話,那么返回則是不確定的.就像未初始化的變量一樣.
?
?
通過時鐘來進行時間通知:
在Linux時間相關的文章中寫了這個方式.就是通過定時器的方式
int?timer_create(clockid_t?clockid,struct?sigevent?*restrict?evp,timer_t?*restrict?timerid);
也就是在第二個參數(shù),struct?sigevent中設置其成員:
int?sigev_notify???通知類型
int?sigev_signo????信號類型
union?signval?sigev_value????信號參數(shù)值
void?(*)(union?sigval)??sigev_notify_function???通知函數(shù)
(pthread_attr_t??*)??sigev_notify_attributes????通知屬性
sigev_notify能夠取值為
SIGEV_NONE????不生成異步信號
SIGEV_SIGNAL??生成排隊的信號,并隨信號攜帶一個應用定義的值,由于是排隊的,所以一定是實時信號,或者說可靠信號.
SIGEV_THREAD??運行一個通知函數(shù),這個是線程里面的高級使用方法,在線程那篇我會補充上的.
.
這里說的就是用SIGEV_THREAD這個宏.假設對sigev_notify設置了SIGEV_THREAD之后,那么就會使用西面兩個成員.一個是線程的運行函數(shù),一個是線程的屬性.假設這么create之后,其異步事件來的時候,會創(chuàng)建一個新線程,屬性為第二參數(shù)的sigev_notify_attributes,運行開始地址為sigev_notify_function.給線程開始函數(shù)的參數(shù)就是sigev_value.
?
而且在這個時候,sigev_signo能夠不用寫.這也是為什么我在上面寫”異步事件”的原因
本文轉自mfrbuaa博客園博客,原文鏈接:http://www.cnblogs.com/mfrbuaa/p/5315046.html,如需轉載請自行聯(lián)系原作者
總結
以上是生活随笔為你收集整理的Linux编程---线程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CCF-IFAA基金海外参展 全球安全盛
- 下一篇: SElinux测试及排错