Linux多執行緒程式設計入門 2

2021-05-23 13:12:10 字數 4292 閱讀 3208

執行緒的分離狀態決定乙個執行緒以什麼樣的方式來終止自己。在上面的例子中,我們採用了執行緒的預設屬性,即為非分離狀態,這種情況下,原有的執行緒等待建立的執行緒結束。只有當

pthread_join

()函式返回時,建立的執行緒才算終止,才能釋放自己占用的系統資源。而分離執行緒不是這樣子的,它沒有被其他的執行緒所等待,自己執行結束了,執行緒也就終止了,馬上釋放系統資源。程式設計師應該根據自己的需要,選擇適當的分離狀態。設定執行緒分離狀態的函式為

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

()之類的函式,它們是使整個程序睡眠,並不能解決執行緒同步的問題。

另外乙個可能常用的屬性是執行緒的優先順序,它存放在結構

sched_param

中。用函式

pthread_attr_getschedparam

和函式pthread_attr_setschedparam

進行存放,一般說來,我們總是先取優先順序,對取得的值修改後再存放回去。下面即是一段簡單的例子。

執行緒的資料處理

和程序相比,執行緒的最大優點之一是資料的共享性,各個程序共享父程序處沿襲的資料段,可以方便的獲得、修改資料。但這也給多執行緒程式設計帶來了許多問題。我們必須當心有多個不同的程序訪問相同的變數。許多函式是不可重入的,即同時不能執行乙個函式的多個拷貝(除非使用不同的資料段)。在函式中宣告的靜態變數常常帶來問題,函式的返回值也會有問題。因為如果返回的是函式內部靜態宣告的空間的位址,則在乙個執行緒呼叫該函式得到位址後使用該位址指向的資料時,別的執行緒可能呼叫此函式並修改了這一段資料。在程序中共享的變數必須用關鍵字

volatile

來定義,這是為了防止編譯器在優化時(如

gcc中使用

-ox引數)改變它們的使用方式。為了保護變數,我們必須使用訊號量、互斥等方法來保證我們對變數的正確使用。下面,我們就逐步介紹處理執行緒資料時的有關知識。 1

、執行緒資料

在單執行緒的程式裡,有兩種基本的資料:全域性變數和區域性變數。但在多執行緒程式裡,還有第三種資料型別:執行緒資料(

tsd: thread-specific data

errno

,它返回標準的出錯資訊。它顯然不能是乙個區域性變數,幾乎每個函式都應該可以呼叫它;但它又不能是乙個全域性變數,否則在

a執行緒裡輸出的很可能是

b執行緒的出錯資訊。要實現諸如此類的變數,我們就必須使用執行緒資料。我們為每個執行緒資料建立乙個鍵,它和這個鍵相關聯,在各個執行緒裡,都使用這個鍵來指代執行緒資料,但在不同的執行緒裡,這個鍵代表的資料是不同的,在同乙個執行緒裡,它代表同樣的資料內容。

和執行緒資料相關的函式主要有

4個:建立乙個鍵;為乙個鍵指定執行緒資料;從乙個鍵讀取執行緒資料;刪除鍵。

建立鍵的函式原型為:

第乙個引數為指向乙個鍵值的指標,第二個引數指明了乙個

destructor

函式,如果這個引數不為空,那麼當每個執行緒結束時,系統將呼叫這個函式來釋放繫結在這個鍵上的記憶體塊。這個函式常和函式

pthread_once ((pthread_once_t*once_control, void (*initroutine) (void)))

一起使用,為了讓這個鍵只被建立一次。函式

pthread_once

宣告乙個初始化函式,第一次呼叫

pthread_once

時它執行這個函式,以後的呼叫將被它忽略。

在下面的例子中,我們建立乙個鍵,並將它和某個資料相關聯。我們要定義乙個函式

createwindow

,這個函式定義乙個圖形視窗(資料型別為

fl_window *

,這是圖形介面開發工具

fltk

中的資料型別)。由於各個執行緒都會呼叫這個函式,所以我們使用執行緒資料。

這樣,在不同的執行緒中呼叫函式

createmywin

pthread_getspecific

得到。在上面的例子中,我們已經使用了函式

pthread_setspecific

來將執行緒資料和乙個鍵繫結在一起。這兩個函式的原型如下:

這兩個函式的引數意義和使用方法是顯而易見的。要注意的是,用

pthread_setspecific

為乙個鍵指定新的執行緒資料時,必須自己釋放原有的執行緒資料以**空間。這個過程函式

pthread_key_delete

用來刪除乙個鍵,這個鍵占用的記憶體將被釋放,但同樣要注意的是,它只釋放鍵占用的記憶體,並不釋放該鍵關聯的執行緒資料所占用的記憶體資源,而且它也不會觸發函式

pthread_key_create

中定義的

destructor

函式。執行緒資料的釋放必須在釋放鍵之前完成。

2、互斥鎖

互斥鎖用來保證一段時間內只有乙個執行緒在執行一段**。必要性顯而易見:假設各個執行緒向同乙個檔案順序寫入資料,最後得到的結果一定是災難性的。

我們先看下面一段**。這是乙個讀

/寫程式,它們公用乙個緩衝區,並且我們假定乙個緩衝區只能儲存一條資訊。即緩衝區只有兩個狀態:有資訊或沒有資訊。

這裡宣告了互斥鎖變數

mutex

,結構pthread_mutex_t

為不公開的資料型別,其中包含乙個系統分配的屬性物件。函式

pthread_mutex_init

用來生成乙個互斥鎖。

null

引數表明使用預設屬性。如果需要宣告特定屬性的互斥鎖,須呼叫函式

pthread_mutexattr_init

。函式pthread_mutexattr_setpshared

和函式pthread_mutexattr_settype

用來設定互斥鎖屬性。前乙個函式設定屬性

pshared

,它有兩個取值,

pthread_process_private

和pthread_process_shared

。前者用來不同程序中的執行緒同步,後者用於同步本程序的不同執行緒。在上面的例子中,我們使用的是預設屬性

pthread_process_ private

。後者用來設定互斥鎖型別,可選的型別有

pthread_mutex_normal

、pthread_mutex_errorcheck

、pthread_mutex_recursive

和pthread _mutex_default

。它們分別定義了不同的上所、解鎖機制,一般情況下,選用最後乙個預設屬性。

pthread_mutex_lock

宣告開始用互斥鎖上鎖,此後的**直至呼叫

pthread_mutex_unlock

為止,均被上鎖,即同一時間只能被乙個執行緒呼叫執行。當乙個執行緒執行到

pthread_mutex_lock

處時,如果該鎖此時被另乙個執行緒使用,那此執行緒被阻塞,即程式將等待到另乙個執行緒釋放此互斥鎖。在上面的例子中,我們使用了

pthread_delay_np

函式,讓執行緒睡眠一段時間,就是為了防止乙個執行緒始終佔據此函式。

上面的例子非常簡單,就不再介紹了,需要提出的是在使用互斥鎖的過程中很有可能會出現死鎖:兩個執行緒試圖同時占用兩個資源,並按不同的次序鎖定相應的互斥鎖,例如兩個執行緒都需要鎖定互斥鎖

1和互斥鎖2,

a執行緒先鎖定互斥鎖1,

b執行緒先鎖定互斥鎖

2,這時就出現了死鎖。此時我們可以使用函式

pthread_mutex_trylock

,它是函式

pthread_mutex_lock

的非阻塞版本,當它發現死鎖不可避免時,它會返回相應的資訊,程式設計師可以針對死鎖做出相應的處理。另外不同的互斥鎖型別對死鎖的處理不一樣,但最主要的還是要程式設計師自己在程式設計注意這一點。

Linux 多執行緒程式設計入門

建立執行緒 intpthread create pthread t restrict thread,const pthread attr t restrict attr,void start routine void void restrict arg 引數 thread 輸出執行緒id attr ...

Linux多執行緒程式設計入門 3

3 條件變數 前一節中我們講述了如何使用互斥鎖來實現執行緒間資料的共享和通訊,互斥鎖乙個明顯的缺點是它只有兩種狀態 鎖定和非鎖定。而條件變數通過允許執行緒阻塞和等待另乙個執行緒傳送訊號的方法彌補了互斥鎖的不足,它常和互斥鎖一起使用。使用時,條件變數被用來阻塞乙個執行緒,當條件不滿足時,執行緒往往解開...

Linux多執行緒程式設計(2)執行緒同步

執行緒同步 a.mutex 互斥量 多個執行緒同時訪問共享資料時可能會衝突,這跟前面講訊號時所說的可重要性是同樣的問 題。假如 兩個執行緒都要把某個全域性變數增加1,這個操作在某平台需要三條指令完成 1.從記憶體讀變數值到暫存器 2.暫存器的值加1 3.將暫存器的值寫回記憶體 我們通過乙個簡單的程式...