[英]how to make an application thread safe?
我認為線程安全,特別是意味着它必須滿足多線程訪問相同共享數據的需要。 但是,似乎這個定義還不夠。
任何人都可以列出要完成或處理的事情,以使應用程序線程安全 。 如果可能的話,給出一個關於C / C ++語言的答案。
有幾種方法可以使函數成為線程安全的。
它可以是可重入的 。 這意味着函數沒有狀態,並且不接觸任何全局變量或靜態變量,因此可以同時從多個線程調用它。 該術語來自允許一個線程進入該函數而另一個線程已經在其中。
它可以有一個關鍵部分 。 這個術語被拋出很多,但坦率地說我更喜歡關鍵數據 。 每當您的代碼觸及跨多個線程共享的數據時,就會出現一個關鍵部分。 所以我更喜歡把重點放在關鍵數據上。
如果正確使用互斥鎖 ,則可以同步對關鍵數據的訪問,從而正確保護線程不安全的修改。 互斥鎖和鎖是非常有用的,但強大的功能帶來了巨大的責任。 您不能在同一個線程中兩次鎖定相同的互斥鎖(這是一個自死鎖)。 如果您獲得多個互斥鎖,則必須小心,因為這會增加死鎖的風險。 您必須使用互斥鎖持續保護數據。
如果所有函數都是線程安全的,並且所有共享數據都受到適當保護,那么您的應用程序應該是線程安全的。
正如Crazy Eddie所說,這是一個很大的主題。 我建議閱讀boost線程,並相應地使用它們。
低級警告 :編譯器可以重新排序語句,這可能會破壞線程的安全性。 對於多個內核,每個內核都有自己的緩存,您需要正確同步緩存以確保線程安全。 此外,即使編譯器沒有重新排序語句,硬件也可能。 因此,今天實際上不可能完全保證線程安全。 你可以獲得99.99%的方式,並且編譯器供應商和cpu制造商正在努力解決這個揮之不去的警告。
無論如何,如果你正在尋找一個清單來使類線程安全:
boost::mutex m_mutex
並在您嘗試訪問該共享成員數據時使用它(理想情況下,共享數據對於該類是私有的,因此您可以更確定您是否正確保護它)。 static
關鍵字。 它實際上不是線程安全的。 所以,如果你正在嘗試做一個單身,它將無法正常工作。 這是一份不完整的清單。 如果我想到它,我會添加更多,但希望它足以讓你開始。
兩件事情:
例如,如果我們開始:
// Globals
int x;
int y;
// Function that needs to be accessed by multiple threads
// currently relies on globals, and hence cannot work with
// multiple threads
int myFunc()
{
return x+y;
}
一旦我們添加了一個狀態結構,代碼就會變成:
typedef struct myState
{
int x;
int y;
} myState;
// Function that needs to be accessed by multiple threads
// now takes state struct
int myFunc(struct myState *state)
{
return (state->x + state->y);
}
現在您可能會問為什么不將x和y作為參數傳遞。 原因是這個例子是一個簡化。 在現實生活中,你的狀態結構可能有20個字段,並且通過大多數這些參數4-5個函數變得令人生畏。 你寧願傳遞一個參數而不是許多參數。
如果要對類的方法進行獨占訪問,則必須在這些函數中使用鎖。
不同類型的鎖:
使用atomic_flg_lck:
class SLock
{
public:
void lock()
{
while (lck.test_and_set(std::memory_order_acquire));
}
void unlock()
{
lck.clear(std::memory_order_release);
}
SLock(){
//lck = ATOMIC_FLAG_INIT;
lck.clear();
}
private:
std::atomic_flag lck;// = ATOMIC_FLAG_INIT;
};
使用原子:
class SLock
{
public:
void lock()
{
while (lck.exchange(true));
}
void unlock()
{
lck = true;
}
SLock(){
//lck = ATOMIC_FLAG_INIT;
lck = false;
}
private:
std::atomic<bool> lck;
};
使用互斥鎖:
class SLock
{
public:
void lock()
{
lck.lock();
}
void unlock()
{
lck.unlock();
}
private:
std::mutex lck;
};
僅適用於Windows :
class SLock
{
public:
void lock()
{
EnterCriticalSection(&g_crit_sec);
}
void unlock()
{
LeaveCriticalSection(&g_crit_sec);
}
SLock(){
InitializeCriticalSectionAndSpinCount(&g_crit_sec, 0x80000400);
}
private:
CRITICAL_SECTION g_crit_sec;
};
原子和atomic_flag使線程保持旋轉計數。 Mutex只是睡覺了。 如果等待時間太長也許最好睡眠線程。 最后一個“ CRITICAL_SECTION ”使線程保持旋轉計數直到消耗時間,然后線程進入休眠狀態。
如何使用這些關鍵部分?
unique_ptr<SLock> raiilock(new SLock());
class Smartlock{
public:
Smartlock(){ raiilock->lock(); }
~Smartlock(){ raiilock->unlock(); }
};
使用raii成語。 用於鎖定關鍵部分的構造函數和用於解鎖它的析構函數。
例
class MyClass {
void syncronithedFunction(){
Smartlock lock;
//.....
}
}
此實現是線程安全且異常安全的,因為變量鎖保存在堆棧中,因此當函數作用域結束(函數結束或異常)時,將調用析構函數。
我希望你覺得這很有幫助。
謝謝!!
一個想法是將您的程序視為一系列通過隊列進行換向的線程。 每個線程都有一個隊列,這些隊列將與所有線程共享(以及共享數據同步方法(如互斥等))。
然后“解決”生產者/消費者問題,但是你想讓隊列保持下溢或溢出。 http://en.wikipedia.org/wiki/Producer-consumer_problem
只要你保持你的線程本地化,只是通過隊列發送副本來共享數據,而不是訪問多線程中的(大多數)gui庫和靜態變量等線程不安全的東西,那么你應該沒問題。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.