C++11使用互斥量保护共享数据
C++中使用互斥量
在C++11中,可以通過(guò)實(shí)例化std::mutex創(chuàng)建互斥量,可以通過(guò)調(diào)用成員函數(shù)lock()進(jìn)行上鎖,調(diào)用unlock()進(jìn)行解鎖。
例如:
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';//若在這期間,發(fā)生異常,將導(dǎo)致鎖無(wú)法釋放,異常//解鎖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();}不過(guò),不推薦實(shí)踐中直接去調(diào)用成員函數(shù),因?yàn)檎{(diào)用成員函數(shù)就意味著,必須記住在每個(gè)函數(shù)出口都要去調(diào)用unlock(),也包括異常的情況,如果處理不當(dāng),將導(dǎo)致鎖無(wú)法釋放,程序卡住。
因此,C++庫(kù)為互斥量提供了一個(gè)RAII語(yǔ)法的模板類std::lock_guard,其會(huì)在構(gòu)造的時(shí)候提供已鎖的互斥量,并在析構(gòu)的時(shí)候進(jìn)行解鎖,從而保證了一個(gè)已鎖的互斥量總是會(huì)被正確的解鎖。對(duì)于以上的代碼,我們只需要將全局變量用lock_guard鎖即可大大提高程序的健壯性。
修改代碼如下:
for (int i = 0; i < 3; ++i) {//使用RAII語(yǔ)法,提高程序的健壯性std::lock_guard<std::mutex> gurad(g_num_mutex);++g_num;... }基于面向?qū)ο鬁?zhǔn)則,保護(hù)共享數(shù)據(jù)
雖然某些情況下,使用全局變量沒(méi)問(wèn)題,但在大多數(shù)情況下,互斥量通常會(huì)與保護(hù)的數(shù)據(jù)放在同一個(gè)類中,而不是定義成全局變量。這是面向?qū)ο笤O(shè)計(jì)的準(zhǔn)則:將互斥量放在一個(gè)類中,對(duì)類的功能進(jìn)行封裝,并進(jìn)行數(shù)據(jù)保護(hù)。在這種情況下,互斥量和要保護(hù)的數(shù)據(jù)都被定義成private成員,這會(huì)讓訪問(wèn)數(shù)據(jù)的代碼變的清晰。
可靠的代碼設(shè)計(jì)
進(jìn)行代碼設(shè)計(jì)時(shí),一定要特別注意不要返回受保護(hù)數(shù)據(jù)的指針或者引用,否則數(shù)據(jù)安全是不可以靠的,例如:
template<typename T> class CThreadsafeStack { public://不要設(shè)計(jì)這樣的接口,是不安全行為//因?yàn)橛脩艨梢栽陬愅庑薷臄?shù)據(jù)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;//受保護(hù)的數(shù)據(jù) };接口內(nèi)在的條件競(jìng)爭(zhēng)
在C++ STL模板庫(kù)中,已經(jīng)實(shí)現(xiàn)stack功能,但是std::stack<>卻不是線程安全的,因?yàn)閟tack成員函數(shù)存在惡性條件的數(shù)據(jù)競(jìng)爭(zhēng),導(dǎo)致多線程環(huán)境下,數(shù)據(jù)出現(xiàn)錯(cuò)誤。
例如:
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();//自己實(shí)現(xiàn)的打印函數(shù),保證打印互斥,//可以用普通的printf函數(shù)實(shí)現(xiàn),只是打印可能不完整safe_print(value); });std::thread td2([&](){int value = st.top();//其他操作std::this_thread::sleep_for(std::chrono::milliseconds(5));st.pop();//自己實(shí)現(xiàn)的打印函數(shù),保證打印互斥。safe_print(value); }); td1.join(); td2.join();?運(yùn)行結(jié)果:
get top value is :300 get top value is :300由于接口之間沒(méi)有鎖機(jī)制或者鎖的范圍太小,導(dǎo)致兩個(gè)線程對(duì)同一個(gè)數(shù)據(jù)進(jìn)行兩次讀取(都pop之前進(jìn)行top操作),導(dǎo)致進(jìn)行數(shù)據(jù)處理的時(shí)候就出現(xiàn)異常情況(一個(gè)數(shù)據(jù)處理兩次)。
從以上實(shí)驗(yàn)以及線程時(shí)間片分析,top()和pop()之間存在惡性條件競(jìng)爭(zhēng),因?yàn)殒i的粒度太小(接口內(nèi)有鎖或者干脆沒(méi)有鎖機(jī)制),需要保護(hù)的操作并未全覆蓋到導(dǎo)致。
如果需要達(dá)到預(yù)期效果,需要有這么一個(gè)鎖,它能鎖住top和pop兩個(gè)操作才可以,這樣鎖的粒度就變大了,從設(shè)計(jì)上來(lái)說(shuō),并不合理。因?yàn)槿绻粋€(gè)系統(tǒng)中鎖的粒度太大,一個(gè)線程需要等待較長(zhǎng)時(shí)間,導(dǎo)致系統(tǒng)的并發(fā)性能就受到了限制。因此原有的stack提供的接口在多線程環(huán)境中,顯得并不是那么好。
為了達(dá)到線程安全以及符合棧的基本操作,需要重新設(shè)計(jì)線程安全的棧類。
代碼如下:
測(cè)試代碼:
CThreadsafeStack <int> st; //僅一個(gè)數(shù)據(jù) 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();運(yùn)行結(jié)果:
//彈出一個(gè)數(shù)據(jù),另外一個(gè)報(bào)異常 符合預(yù)期 get value is :1 empty stack!.原文鏈接:https://blog.csdn.net/c_base_jin/article/details/89440786
總結(jié)
以上是生活随笔為你收集整理的C++11使用互斥量保护共享数据的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: C++11之std::async使用介绍
- 下一篇: C++17下map不常用的接口函数汇总