C++11使用互斥量保护共享数据
C++中使用互斥量
在C++11中,可以通過實例化std::mutex創建互斥量,可以通過調用成員函數lock()進行上鎖,調用unlock()進行解鎖。
例如:
int g_num = 0; std::mutex g_num_mutex;void slow_increment(int id) {for (int i = 0; i < 3; ++i){//加鎖g_num_mutex.lock();++g_num;std::cout << id << " => " << g_num << '\n';//若在這期間,發生異常,將導致鎖無法釋放,異常//解鎖g_num_mutex.unlock();std::this_thread::sleep_for(std::chrono::seconds(1));} }int _tmain(int argc, _TCHAR* argv[]) {std::thread td1(slow_increment, 0);std::thread td2(slow_increment, 0);td1.join();td2.join();}不過,不推薦實踐中直接去調用成員函數,因為調用成員函數就意味著,必須記住在每個函數出口都要去調用unlock(),也包括異常的情況,如果處理不當,將導致鎖無法釋放,程序卡住。
因此,C++庫為互斥量提供了一個RAII語法的模板類std::lock_guard,其會在構造的時候提供已鎖的互斥量,并在析構的時候進行解鎖,從而保證了一個已鎖的互斥量總是會被正確的解鎖。對于以上的代碼,我們只需要將全局變量用lock_guard鎖即可大大提高程序的健壯性。
修改代碼如下:
for (int i = 0; i < 3; ++i) {//使用RAII語法,提高程序的健壯性std::lock_guard<std::mutex> gurad(g_num_mutex);++g_num;... }基于面向對象準則,保護共享數據
雖然某些情況下,使用全局變量沒問題,但在大多數情況下,互斥量通常會與保護的數據放在同一個類中,而不是定義成全局變量。這是面向對象設計的準則:將互斥量放在一個類中,對類的功能進行封裝,并進行數據保護。在這種情況下,互斥量和要保護的數據都被定義成private成員,這會讓訪問數據的代碼變的清晰。
可靠的代碼設計
進行代碼設計時,一定要特別注意不要返回受保護數據的指針或者引用,否則數據安全是不可以靠的,例如:
template<typename T> class CThreadsafeStack { public://不要設計這樣的接口,是不安全行為//因為用戶可以在類外修改數據std::stack<T>& GetData(){std::lock_guard<std::mutex> guard(m_mutex);return m_data;}private:std::mutex m_mutex; //互斥量std::stack<T> m_data;//受保護的數據 };接口內在的條件競爭
在C++ STL模板庫中,已經實現stack功能,但是std::stack<>卻不是線程安全的,因為stack成員函數存在惡性條件的數據競爭,導致多線程環境下,數據出現錯誤。
例如:
std::stack<int> st; st.push(100); st.push(200); st.push(300);std::thread td1([&](){int value = st.top(); //其他操作std::this_thread::sleep_for(std::chrono::milliseconds(5));st.pop();//自己實現的打印函數,保證打印互斥,//可以用普通的printf函數實現,只是打印可能不完整safe_print(value); });std::thread td2([&](){int value = st.top();//其他操作std::this_thread::sleep_for(std::chrono::milliseconds(5));st.pop();//自己實現的打印函數,保證打印互斥。safe_print(value); }); td1.join(); td2.join();?運行結果:
get top value is :300 get top value is :300由于接口之間沒有鎖機制或者鎖的范圍太小,導致兩個線程對同一個數據進行兩次讀取(都pop之前進行top操作),導致進行數據處理的時候就出現異常情況(一個數據處理兩次)。
從以上實驗以及線程時間片分析,top()和pop()之間存在惡性條件競爭,因為鎖的粒度太小(接口內有鎖或者干脆沒有鎖機制),需要保護的操作并未全覆蓋到導致。
如果需要達到預期效果,需要有這么一個鎖,它能鎖住top和pop兩個操作才可以,這樣鎖的粒度就變大了,從設計上來說,并不合理。因為如果一個系統中鎖的粒度太大,一個線程需要等待較長時間,導致系統的并發性能就受到了限制。因此原有的stack提供的接口在多線程環境中,顯得并不是那么好。
為了達到線程安全以及符合棧的基本操作,需要重新設計線程安全的棧類。
代碼如下:
測試代碼:
CThreadsafeStack <int> st; //僅一個數據 st.push(1); std::function <void (int)> func = [&](int timeout) {try{if (!st.empty()){std::this_thread::sleep_for(std::chrono::milliseconds(timeout));int nValue = 0;st.pop(nValue);safe_print(nValue);}}catch (empty_stack &e){std::cout << e.what() << std::endl;} };std::thread td1(func,2); std::thread td2(func,3);td1.join(); td2.join();運行結果:
//彈出一個數據,另外一個報異常 符合預期 get value is :1 empty stack!.原文鏈接:https://blog.csdn.net/c_base_jin/article/details/89440786
總結
以上是生活随笔為你收集整理的C++11使用互斥量保护共享数据的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++11之std::async使用介绍
- 下一篇: 佳明发布两款GPS运动智能腕表:首搭AM