2011-12-14 36 views
0

我構建了一個小應用程序,該應用程序具有一個渲染線程和一些輔助線程,用於可在渲染附近進行的任務,例如,將文件上傳到某臺服務器上。現在,在這些工作線程中,我使用不同的對象來存儲反饋信息,並與渲染線程共享這些對象以進行輸出目的的讀取。所以render = output,worker = input。這些共享對象是int,float,bool,STL字符串和STL列表。如何以及在多線程C++中必須同步哪些數據

我有這個運行幾個月,除輸出過程中出現2次隨機崩潰外,其他都很好,但我現在已經瞭解了線程同步。我讀int,bool等不需要同步,我認爲它是有道理的,但是當我查看字符串和列表時,如果兩個線程同時嘗試讀取/寫入對象,我擔心潛在的崩潰。基本上我希望一個線程更改字符串的大小,而另一個線程可能會使用過時的大小循環其字符,然後從未分配的內存中讀取。今天晚上,我想用2個線程編寫/讀取循環中的同一個對象來構建一個小測試場景,但是我希望能夠在這裏獲得一些想法。

我在讀關於Win32中的CriticalSection,並認爲它可能值得一試。但我不確定實現它的最佳方式是什麼。如果我在閱讀/功能的開始和結束時放置它,感覺有時浪費了一些時間。如果我在Set和Get函數中爲EnterCriticalSection和LeaveCriticalSection換行,我想要在線程中同步每個對象,它就是很多管理工具。

我想我必須爬過更多的參考。


好吧我還不確定如何繼續。我正在研究StackedCrooked提供的鏈接,但仍然沒有如何做到這一點的圖像。

我現在把這個複製/修改在一起,不知道如何繼續或做什麼:有人有想法?

class CSync 
{ 
public: 
    CSync() 
    : m_isEnter(false) 
    { InitializeCriticalSection(&m_CriticalSection); } 
    ~CSync() 
    { DeleteCriticalSection(&m_CriticalSection); } 
    bool TryEnter() 
    { 
     m_isEnter = TryEnterCriticalSection(&m_CriticalSection)==0 ? false:true; 
     return m_isEnter; 
    } 
    void Enter() 
    { 
     if(!m_isEnter) 
     { 
      EnterCriticalSection(&m_CriticalSection); 
      m_isEnter=true; 
     } 
    } 
    void Leave() 
    { 
     if(m_isEnter) 
     { 
      LeaveCriticalSection(&m_CriticalSection); 
      m_isEnter=false; 
     } 
    } 

private: 
    CRITICAL_SECTION m_CriticalSection; 
    bool m_isEnter; 
}; 

/* not needed 

class CLockGuard 
{ 
public: 
    CLockGuard(CSync& refSync) : m_refSync(refSync) { Lock(); } 
    ~CLockGuard() { Unlock(); } 

private: 
    CSync& m_refSync; 

    CLockGuard(const CLockGuard &refcSource); 
    CLockGuard& operator=(const CLockGuard& refcSource); 
    void Lock() { m_refSync.Enter(); } 
    void Unlock() { m_refSync.Leave(); } 
};*/ 

template<class T> class Wrap 
{ 
public: 
    Wrap(T* pp, const CSync& sync) 
     : p(pp) 
     , m_refSync(refSync) 
    {} 
    Call_proxy<T> operator->() { m_refSync.Enter(); return Call_proxy<T>(p); } 
private: 
    T* p; 
    CSync& m_refSync; 
}; 

template<class T> class Call_proxy 
{ 
public: 
    Call_proxy(T* pp, const CSync& sync) 
     : p(pp) 
     , m_refSync(refSync) 
    {} 
    ~Call_proxy() { m_refSync.Leave(); } 
    T* operator->() { return p; } 
private: 
    T* p; 
    CSync& m_refSync; 
}; 


int main 
{ 
    CSync sync; 
    Wrap<string> safeVar(new string); 
    // safeVar what now? 
    return 0; 
}; 

好了,所以我準備一個小測試,現在看看我嘗試做一件好事,所以首先我創建了一個安裝程序,使應用程序崩潰,我認爲......

但那不會崩潰!?這是否意味着我不需要同步?程序需要什麼來有效地崩潰?如果它沒有崩潰,爲什麼我甚至打擾。看來我又錯過了一些觀點。有任何想法嗎?

string gl_str, str_test; 

void thread1() 
{ 
    while(true) 
    { 
     gl_str = "12345"; 
     str_test = gl_str; 
    } 
}; 

void thread2() 
{ 
    while(true) 
    { 
     gl_str = "123456789"; 
     str_test = gl_str; 
    } 
}; 

CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)thread1, NULL, 0, NULL); 
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)thread2, NULL, 0, NULL); 

只是增加了更多的東西,現在它調用清除()崩潰。好。

void thread1() 
{ 
    while(true) 
    { 
     gl_str = "12345"; 
     str_test = gl_str; 
     gl_str.clear(); 
     gl_int = 124; 
    } 
}; 

void thread2() 
{ 
    while(true) 
    { 
     gl_str = "123456789"; 
     str_test = gl_str; 
     gl_str.clear(); 
     if(gl_str.empty()) 
      gl_str = "aaaaaaaaaaaaa"; 
     gl_int = 244; 
     if(gl_int==124) 
      gl_str.clear(); 
    } 
}; 
+0

使用``提供的同步原語。實質上,您應該始終將訪問權限同步到多個線程使用的任何容器。 – 2011-12-14 15:30:15

+0

我也讀過關於互斥鎖的內容,它的本質是,如果我只需要一個進程就可以使用它,這將是一種矯枉過正的做法。 CriticalSection速度更快。 http://msdn.microsoft.com/en-us/library/ms810428.aspx – xezon 2011-12-14 16:22:42

回答

5

規則很簡單:如果對象可以在任何線程中修改,那麼對它的所有訪問都需要同步。對象的類型並不重要:即使是boolint也需要某種外部同步(可能通過一種特殊的,系統相關的功能,而不是鎖)。至少在C++中沒有例外。 (如果您願意使用內聯彙編程序,並瞭解圍牆和內存屏障的含義,則可以避免鎖定。)

+1

有在'的std ::原子` – PlasmaHH 2011-12-14 15:49:15

0

首先,即使訪問最原始的數據類型,也需要保護。 如果你有一個int x的地方,你可以寫

x += 42; 

...但是,這將意味着,在最低水平:讀取x的舊值,計算出新的價值,新的值寫入變量X。如果兩個線程幾乎在同一時間執行該操作,則會發生奇怪的事情。您需要鎖定/關鍵部分。

我建議你使用C++ 11和相關接口,或者,如果不可用,相應的東西從升壓::線程庫。如果這不是一個選項,Win32的關鍵部分和Unix的pthread_mutex_ *。

NO,不要開始寫多線程程序尚未!

讓我們先談談不變量。 在(假設的)明確定義的程序中,每個類都有一個不變量。 不變量是關於一個實例的狀態,即關於其所有成員變量值的某個邏輯語句。如果這個不變變得錯誤,那麼這個對象就會被破壞,損壞,程序可能會崩潰,壞事已經發生了。你所有的函數都假設這個不變量在被調用時是真實的,並且確保它在事後仍然是真實的。

當一個成員函數改變一個成員變量,不變量可能會暫時變成假的,但是這是可以的,因爲成員函數將確保一切「組合在一起」在退出前一次。

你需要保護的不變的鎖 - 當你做的東西,可能會影響不變,採取鎖,不鬆開,直到你確信不變恢復。

1

我讀整型,bool等,不需要同步

這是不正確的:

  • 一個線程可以存儲變量的副本在CPU寄存器並繼續使用甚至在原始變量中的舊值已被另一個線程修改。
  • 簡單的操作如i++不是原子的。
  • 編譯器可能會重新排序讀取和寫入變量。這可能會導致多線程場景中的同步問題。
  • 有關更多詳細信息,請參閱Lockless Programming Considerations

應使用互斥來防止競爭條件。請參見this article,以便快速瞭解boost線程庫。