簡體   English   中英

如何使應用程序線程安全?

[英]how to make an application thread safe?

我認為線程安全,特別是意味着它必須滿足多線程訪問相同共享數據的需要。 但是,似乎這個定義還不夠。

任何人都可以列出要完成或處理的事情,以使應用程序線程安全 如果可能的話,給出一個關於C / C ++語言的答案。

有幾種方法可以使函數成為線程安全的。

它可以是可重入的 這意味着函數沒有狀態,並且不接觸任何全局變量或靜態變量,因此可以同時從多個線程調用它。 該術語來自允許一個線程進入該函數而另一個線程已經在其中。

它可以有一個關鍵部分 這個術語被拋出很多,但坦率地說我更喜歡關鍵數據 每當您的代碼觸及跨多個線程共享的數據時,就會出現一個關鍵部分。 所以我更喜歡把重點放在關鍵數據上。

如果正確使用互斥鎖 ,則可以同步對關鍵數據的訪問,從而正確保護線程不安全的修改。 互斥鎖和鎖是非常有用的,但強大的功能帶來了巨大的責任。 您不能在同一個線程中兩次鎖定相同的互斥鎖(這是一個自死鎖)。 如果您獲得多個互斥鎖,則必須小心,因為這會增加死鎖的風險。 您必須使用互斥鎖持續保護數據。

如果所有函數都是線程安全的,並且所有共享數據都受到適當保護,那么您的應用程序應該是線程安全的。

正如Crazy Eddie所說,這是一個很大的主題。 我建議閱讀boost線程,並相應地使用它們。

低級警告 :編譯器可以重新排序語句,這可能會破壞線程的安全性。 對於多個內核,每個內核都有自己的緩存,您需要正確同步緩存以確保線程安全。 此外,即使編譯器沒有重新排序語句,硬件也可能。 因此,今天實際上不可能完全保證線程安全。 你可以獲得99.99%的方式,並且編譯器供應商和cpu制造商正在努力解決這個揮之不去的警告。

無論如何,如果你正在尋找一個清單來使類線程安全:

  • 識別跨線程共享的任何數據(如果您錯過了,則無法保護它)
  • 創建一個成員boost::mutex m_mutex並在您嘗試訪問該共享成員數據時使用它(理想情況下,共享數據對於該類是私有的,因此您可以更確定您是否正確保護它)。
  • 清理全局變量。 不管怎樣,Globals都很糟糕,並試圖用全局變量做任何線程安全的運氣。
  • 請注意static關鍵字。 它實際上不是線程安全的。 所以,如果你正在嘗試做一個單身,它將無法正常工作。
  • 當心雙重鎖定范例。 大多數使用它的人都會以某種微妙的方式弄錯,並且很容易受到低級警告的破壞。

這是一份不完整的清單。 如果我想到它,我會添加更多,但希望它足以讓你開始。

兩件事情:

1.確保不使用全局變量。 如果你當前有全局變量,那么讓它們成為每線程狀態結構的成員,然后讓線程將結構傳遞給公共函數。

例如,如果我們開始:

// 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個函數變得令人生畏。 你寧願傳遞一個參數而不是許多參數。

2.如果您的線程有共同的數據需要共享,那么您需要查看關鍵部分和信號量。 每當你的一個線程訪問數據時,它就需要阻塞其他線程,然后在訪問共享數據時取消阻塞它們。

如果要對類的方法進行獨占訪問,則必須在這些函數中使用鎖。

不同類型的鎖:

使用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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM