線程安全
線程安全是程式設計中的術語,指某個函數、函式庫在多線程環境中被呼叫時,能夠正確地處理多個線程之間的公用變數,使程式功能正確完成。
假設有間銀行只有 1000 元,而兩個人同時提領 1000 元時就可能會拿到總計 2000 元的金額。為了避免這個問題,該間銀行提款時應該使用互斥鎖,即意味着針對同一個資源處理時,前一個人提領交易完成後才處理下一筆交易。但這種手法會使得效能降低。
一般來說,線程安全的函數應該為每個呼叫它的線程分配專門的空間,來儲存需要單獨儲存的狀態(如果需要的話),不依賴於「線程慣性」,把多個線程共用的變數正確對待(如,通知編譯器該變數為「易失(volatile)」型,阻止其進行一些不恰當的最佳化),而且,線程安全的函數一般不應該修改全域對象。
很多C庫代碼(比如某些strtok的實現,它將「多次呼叫中需要保持不變的狀態」儲存在靜態變數中,導致不恰當的共用)不是線程安全的,在多線程環境中呼叫這些函數時,要進行特別的預防措施,或者尋找別的替代方案。
例子
編輯在下面這段代碼中,函數increment_counter是線程安全的,但不是可重入的。
#include <pthread.h>
int increment_counter ()
{
static int counter = 0;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
// only allow one thread to increment at a time
++counter;
// store value before any other threads increment it further
int result = counter;
pthread_mutex_unlock(&mutex);
return result;
}
上面的代碼中,函數increment_counter可以在多個線程中被呼叫,因為有一個互斥鎖mutex來同步對共用變數counter的訪問。但是如果這個函數用在可重入的中斷處理程式中,如果在pthread_mutex_lock(&mutex)和pthread_mutex_unlock(&mutex)之間產生另一個呼叫函數increment_counter的中斷,則會第二次執行此函數,此時由於mutex已被lock,函數會在pthread_mutex_lock(&mutex)處阻塞,並且由於mutex沒有機會被unlock,阻塞會永遠持續下去。簡言之,問題在於 pthread 的 mutex 不可重入。
解決辦法是設定 PTHREAD_MUTEX_RECURSIVE 屬性。然而對於給出的問題而言,專門使用一個 mutex 來保護一次簡單的增量操作顯然過於昂貴,因此c++11中的原子變數提供了一個可使此函數既線程安全又可重入(而且還更簡潔)的替代方案:
#include <atomic>
int increment_counter ()
{
static std::atomic<int> counter(0);
// increment is guaranteed to be done atomically
int result = ++counter;
return result;
}
外部連結
編輯- Thread-safe Tcl Extensions (頁面存檔備份,存於互聯網檔案館)(wiki page)
- Thread-safe design
- Article "Design for thread safety" by Bill Venners
- Article "Write thread-safe servlets" by Phillip Bridgham