執行緒程式設計之同步基礎

2021-07-25 16:12:20 字數 4648 閱讀 6452

所謂同步,即同時起步,協調一致。不同的物件,對"同步"的理解方式略有不同。如,裝置同步,是指在兩個裝置之間規定乙個共同的時間參考;資料庫同步,是指讓兩個或多個資料庫內容保持一致,或者按需要部分保持一致;檔案同步,是指讓兩個或多個資料夾裡的檔案保持一致。等等

而,程式設計中、通訊中所說的同步與生活中大家印象中的同步概念略有差異。"同"字應是指協同、協助、互相配合。主旨在協同步調,按預定的先後次序執行。

同步即協同步調,按預定的先後次序執行。

執行緒同步,指乙個執行緒發出某一功能呼叫時,在沒有完成工作、得到結果之前,該呼叫不返回。同時其它執行緒為保證資料一致性,不能呼叫該功能。

舉例1:    銀行存款 5000。櫃檯,折:取3000;提款機,卡:取 3000。剩餘:2000

舉例2: 記憶體中100位元組,執行緒t1欲填入全1, 執行緒t2欲填入全0。但如果t1執行了50個位元組失去cpu,t2執行,會將t1寫過的內容覆蓋。當t1再次獲得cpu繼續    從失去cpu的位置向後寫入1,當執行結束,記憶體中的100位元組,既不是全1,也不是全0。

產生的現象叫做"與時間有關的錯誤"(time related)。為了避免這種資料混亂,執行緒需要同步。

"同步"的目的,是為了避免資料混亂,解決與時間有關的錯誤。實際上,不僅執行緒間需要同步,程序間、訊號間等等都需要同步機制。

因此,所有"多個控制流,共同操作乙個共享資源"的情況,都需要同步。

1. 資源共享(獨享資源則不會)    

2. 排程隨機(意味著資料訪問會出現競爭)    

3. 執行緒間缺乏必要的同步機制。

以上3點中,前兩點不能改變,欲提高效率,傳遞資料,資源必須共享。只要共享資源,就一定會出現競爭。只要存在競爭關係,資料就很容易出現混亂。

所以只能從第三點著手解決。使多個執行緒在訪問共享資源的時候,出現互斥。

linux中提供一把互斥鎖mutex(也稱之為互斥量)。

每個執行緒在對資源操作前都嘗試先加鎖,成功加鎖才能操作,操作結束解鎖。

資源還是共享的,執行緒間也還是競爭的,                            

但通過"鎖"就將資源的訪問變成互斥操作,而後與時間有關的錯誤也不會再產生了。

但,應注意:同一時刻,只能有乙個執行緒持有該鎖。

當a執行緒對某個全域性變數加鎖訪問,b在訪問前嘗試加鎖,拿不到鎖,b阻塞。c執行緒不去加鎖,而直接訪問該全域性變數,依然能夠訪問,但會出現資料混亂。

所以,互斥鎖實質上是作業系統提供的一把"建議鎖"(又稱"協同鎖"),建議程式中有多執行緒訪問共享資源的時候使用該機制。但,並沒有強制限定。

因此,即使有了mutex,如果有執行緒不按規則來訪問資料,依然會造成資料混亂。

pthread_mutex_init函式

pthread_mutex_destroy函式

pthread_mutex_lock函式

pthread_mutex_trylock函式

pthread_mutex_unlock函式

以上5個函式的返回值都是:成功返回0, 失敗返回錯誤號。

pthread_mutex_t 型別,其本質是乙個結構體。為簡化理解,應用時可忽略其實現細節,簡單當成整數看待。

pthread_mutex_t mutex; 變數mutex只有兩種取值1、0。

pthread_mutex_init函式

初始化乙個互斥鎖(互斥量) ---> 初值可看作1

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

參1:傳出引數,呼叫時應傳 &mutex    

restrict關鍵字:只用於限制指標,告訴編譯器,所有修改該指標指向記憶體中內容的操作,只能通過本指標完成。不能通過除本指標以外的其他變數或指標修改

參2:互斥量屬性。是乙個傳入引數,通常傳null,選用預設屬性(執行緒間共享)。 參apue.12.4同步屬性

靜態初始化:如果互斥鎖 mutex 是靜態分配的(定義在全域性,或加了static關鍵字修飾),可以直接使用巨集進行初始化。e.g. 

pthead_mutex_t muetx = pthread_mutex_initializer

動態初始化:區域性變數應採用動態初始化。e.g. pthread_mutex_init(&mutex, null)

pthread_mutex_destroy函式

銷毀乙個互斥鎖

int pthread_mutex_destroy(pthread_mutex_t *mutex);

pthread_mutex_lock函式

加鎖。可理解為將mutex--(或-1)

int pthread_mutex_lock(pthread_mutex_t *mutex);

pthread_mutex_unlock函式

解鎖。可理解為將mutex ++(或+1)

int pthread_mutex_unlock(pthread_mutex_t *mutex);

pthread_mutex_trylock函式

嘗試加鎖

int pthread_mutex_trylock(pthread_mutex_t *mutex);

lock與unlock:

lock嘗試加鎖,如果加鎖不成功,執行緒阻塞,阻塞到持有該互斥量的其他執行緒解鎖為止。

unlock主動解鎖函式,同時將阻塞在該鎖上的所有執行緒全部喚醒,至於哪個執行緒先被喚醒,取決於優先順序、排程演算法。預設:先阻塞、先喚醒。

例如:t1 t2 t3 t4 使用一把mutex鎖。t1加鎖成功,其他執行緒均阻塞,直至t1解鎖。t1解鎖後,t2 t3 t4均被喚醒,並自動再次嘗試加鎖。

可假想mutex鎖 init成功初值為1。    lock 功能是將mutex--。    unlock將mutex++

lock與trylock:

lock加鎖失敗會阻塞,等待鎖釋放。

trylock加鎖失敗直接返回錯誤號(如:ebusy),不阻塞。

看如下程式:該程式是非常典型的,由於共享、競爭而沒有加任何同步機制,導致產生於時間有關的錯誤,造成資料混亂:

#include #include #include void *tfn(void *arg)

return null;

}int main(void)

pthread_join(tid, null);

return 0;

}

修改該程式,使用mutex互斥鎖進行同步。                                        

定義全域性互斥量,初始化init(&m, null)互斥量,新增對應的destroy

兩個執行緒while中,兩次printf前後,分別加lock和unlock

將unlock挪至第二個sleep後,發現交替現象很難出現。

執行緒在操作完共享資源後本應該立即解鎖,但修改後,執行緒抱著鎖睡眠。睡醒解鎖後又立即加鎖,這兩個庫函式本身不會阻塞。

所以在這兩行**之間失去cpu的概率很小。因此,另外乙個執行緒很難得到加鎖的機會

#include #include #include #include #include pthread_mutex_t mutex;      //定義鎖

void *tfn(void *arg)

return null;

}int main(void)

pthread_cancel(tid);

pthread_join(tid, null);

pthread_mutex_destroy(&mutex);

return 0;

}/*執行緒之間共享資源stdout*/

main 中加flag = 5 將***在while中-- 這時,主線程輸出5次後試圖銷毀鎖,但子執行緒未將鎖釋放,無法完成。

main 中加pthread_cancel()將子執行緒取消。

#include #include #include #include #include pthread_mutex_t mutex;

void err_thread(int ret, char *str)

}void *tfn(void *arg)

return null;

}int main(void)

pthread_cancel(tid); // 將子執行緒殺死,子執行緒中自帶取消點

pthread_join(tid, null);

pthread_mutex_destroy(&mutex);

return 0; //main中的return可以將整個程序退出

}/*執行緒之間共享資源stdout*/

結論:在訪問共享資源前加鎖,訪問結束後立即解鎖。鎖的"粒度"應越小越好。

執行緒和並行程式設計之執行緒同步(四)

執行緒的目的是為了並行的執行 有時執行緒需要互相等待。例如,如果兩個執行緒同時寫乙個變數,執行結果是未定義的。因此對共享資源的需要進行 同步 處理。qt提供了低階別的 高階別的機制處理執行緒同步。上述同步類可以保證函式的執行緒安全。但是為了保證函式執行緒安全,會有效能的損耗,這也是為什麼qt部分函式...

多執行緒之執行緒同步

pulse lockobj 表示釋放當前被lock的lockobj,容許其他執行緒呼叫。相當於暫時掛起當前執行緒 wait lockobj 表示等待當前被其他執行緒占用的lockobj。下面的 將會交替執行兩個執行緒 class ticktock console.write tick monitor...

執行緒之同步通訊

執行緒之同步通訊 所謂執行緒之同步通訊,為了避免死鎖,讓執行緒在進入堵塞狀態時盡量釋放其鎖定的資源,以為其他的執行緒提供執行的機會 執行緒間的通訊 兩個或兩個以上的執行緒處理同乙個資源,處理的動作是不一樣的。這樣就需要將不同的動作 放到不同的run 方法中,run方法要封裝到單獨的類中。同步中使用 ...