系統程式設計師成長計畫 併發 一 下

2021-05-25 18:47:05 字數 2728 閱讀 5849

作者****:李先靜

linux下的多執行緒程式設計使用pthread(posix thread)函式庫,使用時包含標頭檔案pthread.h,鏈結共享庫libpthread.so。這裡順便說一下gcc鏈結共享庫的方式:-l用來指 定共享庫所在目錄,系統庫目錄不用指定。-l用來指定要鏈結的共享庫,只需要指定庫的名字就行了,如:-lpthread,而不是 -llibpthread.so。看起來有點怪,這樣做的原因是共享庫通常帶有版本號,指定全檔名就意味著你要繫結到特定版本的共享庫上,只指定名字則 在可以執行時通過環境變數來選擇要使用的共享庫,這樣能夠給軟體公升級帶來的方便。

pthread函式庫的使用相對比較簡單,讀者可以在終端下執行man pthread_create閱讀相關函式的手冊,也可以到網上找些例子參考。具體使用方法我們就不講了,這裡介紹一下初學者常犯的錯誤:

o 用臨時變數作為執行緒引數的問題。

#include #include #include void* start_routine(void* param)

pthread_t create_test_thread()

int main(int argc, char* argv)

分析:由於新執行緒和當前執行緒是併發的,誰先誰後是無法**的。可能create_test_thread 已經執行完了,str已經被釋放了,新執行緒才拿到這引數,此時它的內容已經無法確定了,列印出的字串自然是隨機的。

o 執行緒引數共享的問題。

#include #include #include void* start_routine(void* param)

#define threads_nr 10

void create_test_threads()

; for(i = 0; i < threads_nr; i++)

for(i = 0; i < threads_nr; i++)

return ;

}int main(int argc, char* argv)

分析:由於新執行緒和當前執行緒是併發的,誰先誰後是無法**的。i在不斷變化,所以新執行緒拿到的引數值是無法預知的,列印出的字串自然也是隨機的。

o 虛假併發。

#include #include #include void* start_routine(void* param)

#define threads_nr 10

void create_test_threads()

; for(i = 0; i < threads_nr; i++)

return ;

}int main(int argc, char* argv)

分析:因為pthread_join會阻塞直到執行緒退出,所以這些執行緒實際上是序列執行的,乙個退出了,才建立下乙個。當年乙個同事寫了乙個多執行緒的測試程式,就是這樣寫的,結果沒有測試出乙個潛伏的問題,直到產品執行時,這個問題才暴露出來。

訪問執行緒共享的資料時要加鎖,讓訪問序列化,否則就會出問題。比如,可能你正在訪問的雙向鍊錶的某個結點時,它已經被另外乙個執行緒刪掉了。加鎖的方 式有很多種,像互斥鎖(mutex= mutual exclusive lock),訊號量(semaphore)和自旋鎖(spin lock)等都是常用的,它們的使用同樣很簡單,我們就不多說了。

在加鎖/解鎖時,初學者常犯兩個錯誤:

o 存在遺漏解鎖的路徑。初學者常見的做法就是,進入某個臨界函式時加鎖,在函式結尾的地方解鎖,我甚至見過這種寫法:

如果你也犯了這種錯誤,應該好好反思一下。有時候,return的地方太多,在某一處忘記解鎖是可能的,就像記憶體洩露一樣,只是忘記解鎖的後果更嚴重。像下面這個例子:

ret dlist_insert(dlist* thiz, size_t index, void* data)

if(thiz->first == null)

...dlist_unlock(thiz);

return ret_ok;

}

如果乙個函式有五六個甚至更多的地方返回,遺忘一兩個地方是很常見的,即使沒有忘記,每個返回的地方都要去解鎖和釋放相關資源也是很麻煩的。在這種情況下,我們最好是實現單入口單出的函式,常見的做法有兩種:

一種是使用goto語句(在linux核心裡大量使用)。示例如下:

ret dlist_insert(dlist* thiz, size_t index, void* data)

if(thiz->first == null)

...done:

dlist_unlock(thiz);

return ret;

}

另外一種是使用do{}while(0);語句,出於受教科書的影響(不要用goto語句),我習慣了這種做法。示例如下:

ret dlist_insert(dlist* thiz, size_t index, void* data)

if(thiz->first == null)

...}while(0);

dlist_unlock(thiz);

return ret;

}

o 加鎖順序的問題。有時候為了提高效率,常常降低加鎖的粒度,訪問時不是用乙個鎖鎖住整個資料結構,而是用多個鎖來控制資料結構各個部分。這樣乙個執行緒訪問 資料結構的這部分時,另外乙個執行緒還可以訪問資料結構的其它部分。但是在有的情況下,你需要同時鎖幾個鎖,這時就要注意了:所有執行緒一定要按相同的順序加 鎖,相反的順序解鎖。否則就可能出現死鎖,兩個執行緒都拿到對方需要的鎖,結果出現互相等待的情況。

系統程式設計師成長計畫 併發 一 下

作者 李先靜 linux下的多執行緒程式設計使用pthread posix thread 函式庫,使用時包含標頭檔案pthread.h,鏈結共享庫libpthread.so。這裡順便說一下gcc鏈結共享庫的方式 l用來指 定共享庫所在目錄,系統庫目錄不用指定。l用來指定要鏈結的共享庫,只需要指定庫的...

系統程式設計師成長計畫 併發 五

文章出處 作者 李先靜 無鎖 lock free 資料結構 多執行緒併發執行時,雖然有共享資料,如果所有執行緒只是讀取共享資料而不修改它,也是不用加鎖的,比如 段就是共享的 資料 每個執行緒都會讀取,但是不用加鎖。排除所有這些情況,多執行緒之間有共享資料,有的執行緒要修改這些共享資料,有的執行緒要讀...

系統程式設計師成長計畫 併發 五

無鎖 lock free 資料結構 多執行緒併發執行時,雖然有共享資料,如果所有執行緒只是讀取共享資料而不修改它,也是不用加鎖的,比如 段就是共享的 資料 每個執行緒都會讀取,但是不用加鎖。排除所有這些情況,多執行緒之間有共享資料,有的執行緒要修改這些共享資料,有的執行緒要讀取這些共享資料,這才是程...