在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

c++線程中鎖的基本類(lèi)型和用法

科技綠洲 ? 來(lái)源:Linux開(kāi)發(fā)架構(gòu)之路 ? 作者:Linux開(kāi)發(fā)架構(gòu)之路 ? 2023-11-09 15:02 ? 次閱讀

線程之間的鎖有:互斥鎖、條件鎖、自旋鎖、讀寫(xiě)鎖、遞歸鎖。一般而言,鎖的功能與性能成反比。不過(guò)我們一般不使用遞歸鎖(C++標(biāo)準(zhǔn)庫(kù)提供了std::recursive_mutex),所以這里就不推薦了。

互斥鎖(Mutex)

互斥鎖用于控制多個(gè)線程對(duì)他們之間共享資源互斥訪問(wèn)的一個(gè)信號(hào)量。也就是說(shuō)是為了避免多個(gè)線程在某一時(shí)刻同時(shí)操作一個(gè)共享資源。例如線程池中的有多個(gè)空閑線程和一個(gè)任務(wù)隊(duì)列。任何是一個(gè)線程都要使用互斥鎖互斥訪問(wèn)任務(wù)隊(duì)列,以避免多個(gè)線程同時(shí)訪問(wèn)任務(wù)隊(duì)列以發(fā)生錯(cuò)亂。

在某一時(shí)刻,只有一個(gè)線程可以獲取互斥鎖,在釋放互斥鎖之前其他線程都不能獲取該互斥鎖。如果其他線程想要獲取這個(gè)互斥鎖,那么這個(gè)線程只能以阻塞方式進(jìn)行等待。

頭文件:< mutex >

類(lèi)型:std::mutex

用法:在C++中,通過(guò)構(gòu)造std::mutex的實(shí)例創(chuàng)建互斥元,調(diào)用成員函數(shù)lock()來(lái)鎖定它,調(diào)用unlock()來(lái)解鎖,不過(guò)一般不推薦這種做法,標(biāo)準(zhǔn)C++庫(kù)提供了std::lock_guard和unique_lock類(lèi)模板,都是RAII風(fēng)格,它們是在定義時(shí)獲得鎖,在析構(gòu)時(shí)釋放鎖。它們的主要區(qū)別在于unique_lock鎖機(jī)制更加靈活,可以再需要的時(shí)候進(jìn)行l(wèi)ock或者unlock調(diào)用,不非得是析構(gòu)或者構(gòu)造時(shí)。std::mutex和std::lock _ guard。都聲明在< mutex >頭文件中。

圖片

//用互斥元保護(hù)列表
#include
#include

std::list some_list;
std::mutex some_mutex;

void add_to_list(int new_value)
{
std::lock_guard guard(some_mutex);
some_list.push_back(new_value);
}

以下情況會(huì)出現(xiàn)死鎖:

mutex m0,m1;
int i = 0;
void fun0()
{
while (i < 100)
{
lock_guard g0(m0); //線程0加鎖0
lock_guard g1(m1); //線程0加鎖1
cout << "thread 0 running..." << endl;
}
return;
}
void fun1()
{
while (i < 100)
{
lock_guard g1(m1); //線程1加鎖1
lock_guard g0(m0); //線程1加鎖0
cout << "thread 1 running... "<< i << endl;
}
return;
}
int main()
{
thread p0(fun0);
thread p1(fun1);
p0.join();
p1.join();
return 0;
}

死鎖:死鎖是指兩個(gè)或兩個(gè)以上的進(jìn)程(線程)在運(yùn)行過(guò)程中因爭(zhēng)奪資源而造成的一種僵局,若無(wú)外力作用,這些進(jìn)程(線程)都將無(wú)法向前推進(jìn)。

解決死鎖的方法:

1、順序加鎖

mutex m0,m1;
int i = 0;
void fun0()
{
while (i < 100)
{
lock_guard g0(m0); //線程0加鎖0
lock_guard g1(m1); //線程0加鎖1
cout << "thread 0 running..." << endl;
}
return;
}
void fun1()
{
while (i < 100)
{
lock_guard g0(m0); //線程1加鎖0
lock_guard g1(m1); //線程1加鎖1
cout << "thread 1 running... "<< i << endl;
}
return;
}
int main()
{
thread p0(fun0);
thread p1(fun1);
p0.join();
p1.join();
return 0;
}

2、同時(shí)上鎖(需要用到lock函數(shù))++

mutex m0,m1;
int i = 0;
void fun0()
{
while (i < 100)
{
lock(m0,m1);
lock_guard g0(m0, adopt_lock);
lock_guard g1(m1, adopt_lock);
cout << "thread 0 running..." << endl;
}
return;
}
void fun1()
{
while (i < 100)
{
lock(m0,m1);
lock_guard g0(m0, adopt_lock);
lock_guard g1(m1, adopt_lock);
cout << "thread 1 running... "<< i << endl;
}
return;
}
int main()
{
thread p0(fun0);
thread p1(fun1);
p0.join();
p1.join();
return 0;
}

注意到這里的lock_guard中多了第二個(gè)參數(shù)adopt_lock,這個(gè)參數(shù)表示在調(diào)用lock_guard時(shí),已經(jīng)加鎖了,防止lock_guard在對(duì)象生成時(shí)構(gòu)造函數(shù)再次lock()。

條件鎖

當(dāng)需要死循環(huán)判斷某個(gè)條件成立與否時(shí)【true or false】,我們往往需要開(kāi)一個(gè)線程死循環(huán)來(lái)判斷,這樣非常消耗CPU。使用條件變量,可以讓當(dāng)前線程wait,釋放CPU,如果條件改變時(shí),我們?cè)賜otify退出線程,再次進(jìn)行判斷。

條件鎖就是所謂的條件變量,某一個(gè)線程因?yàn)槟硞€(gè)條件未滿足時(shí)可以使用條件變量使該程序處于阻塞狀態(tài)。一旦條件滿足以“信號(hào)量”的方式喚醒一個(gè)因?yàn)樵摋l件而被阻塞的線程(常和互斥鎖配合使用),喚醒后,需要檢查變量,避免虛假喚醒。最為常見(jiàn)就是在線程池中,起初沒(méi)有任務(wù)時(shí)任務(wù)隊(duì)列為空,此時(shí)線程池中的線程因?yàn)椤叭蝿?wù)隊(duì)列為空”這個(gè)條件處于阻塞狀態(tài)。一旦有任務(wù)進(jìn)來(lái),就會(huì)以信號(hào)量的方式喚醒一個(gè)線程來(lái)處理這個(gè)任務(wù)。

頭文件:< condition_variable >

類(lèi)型:std::condition_variable(只和std::mutex一起工作) 和 std::condition_variable_any(符合類(lèi)似互斥元的最低標(biāo)準(zhǔn)的任何東西一起工作)。

圖片

C++標(biāo)準(zhǔn)庫(kù)在< condition_variable >中提供了條件變量,借由它,一個(gè)線程可以喚醒一個(gè)或多個(gè)其他等待中的線程。

想要修改共享變量(即“條件”)的線程必須:

  1. 獲得一個(gè)std::mutex
  2. 當(dāng)持有鎖的時(shí)候,執(zhí)行修改動(dòng)作
  3. 對(duì)std::condition_variable執(zhí)行notify_one或notify_all(當(dāng)做notify動(dòng)作時(shí),不必持有鎖)

即使共享變量是原子性的,它也必須在mutex的保護(hù)下被修改,這是為了能夠?qū)⒏膭?dòng)正確發(fā)布到正在等待的線程。

任意要等待std::condition_variable的線程必須:

  1. 獲取std::unique_lockstd::mutex,這個(gè)mutex正是用來(lái)保護(hù)共享變量(即“條件”)的
  2. 執(zhí)行wait, wait_for或者wait_until. 這些等待動(dòng)作原子性地釋放mutex,并使得線程的執(zhí)行暫停
  3. 當(dāng)獲得條件變量的通知,或者超時(shí),或者一個(gè)虛假的喚醒,那么線程就會(huì)被喚醒,并且獲得mutex. 然后線程應(yīng)該檢查條件是否成立,如果是虛假喚醒,就繼續(xù)等待。

【注:所謂虛假喚醒,就是因?yàn)槟撤N未知的罕見(jiàn)的原因,線程被從等待狀態(tài)喚醒了,但其實(shí)共享變量(即條件)并未變?yōu)閠rue。因此此時(shí)應(yīng)繼續(xù)等待】

std::deque q;
std::mutex mu;
std::condition_variable cond;

void function_1() //生產(chǎn)者
{
int count = 10;
while (count > 0)
{
std::unique_lock locker(mu);
q.push_front(count);
locker.unlock();
cond.notify_one(); // Notify one waiting thread, if there is one.
std::this_thread::sleep_for(std::chrono::seconds(1));
count--;
}
}

void function_2() //消費(fèi)者
{
int data = 0;
while (data != 1)
{
std::unique_lock locker(mu);
while (q.empty())
cond.wait(locker); // Unlock mu and wait to be notified
data = q.back();
q.pop_back();
locker.unlock();
std::cout << "t2 got a value from t1: " << data << std::endl;
}
}
int main()
{
std::thread t1(function_1);
std::thread t2(function_2);
t1.join();
t2.join();
return 0;
}

上面的代碼有三個(gè)注意事項(xiàng):

1.在function_2中,在判斷隊(duì)列是否為空的時(shí)候,使用的是while(q.empty()),而不是if(q.empty()),這是因?yàn)閣ait()從阻塞到返回,不一定就是由于notify_one()函數(shù)造成的,還有可能由于系統(tǒng)的不確定原因喚醒(可能和條件變量的實(shí)現(xiàn)機(jī)制有關(guān)),這個(gè)的時(shí)機(jī)和頻率都是不確定的,被稱(chēng)作偽喚醒。如果在錯(cuò)誤的時(shí)候被喚醒了,執(zhí)行后面的語(yǔ)句就會(huì)錯(cuò)誤,所以需要再次判斷隊(duì)列是否為空,如果還是為空,就繼續(xù)wait()阻塞;

2.在管理互斥鎖的時(shí)候,使用的是std::unique_lock而不是std::lock_guard, 而且事實(shí)上也不能使用std::lock_guard。這需要先解釋下wait()函數(shù)所做的事情,可以看到,在wait()函數(shù)之前,使用互斥鎖保護(hù)了,如果wait的時(shí)候什么都沒(méi)做,豈不是一直持有互斥鎖?那生產(chǎn)者也會(huì)一直卡住,不能夠?qū)?shù)據(jù)放入隊(duì)列中了。所以,wait()函數(shù)會(huì)先調(diào)用互斥鎖的unlock()函數(shù),然后再將自己睡眠,在被喚醒后,又會(huì)繼續(xù)持有鎖,保護(hù)后面的隊(duì)列操作。lock_guard沒(méi)有l(wèi)ock和unlock接口,而unique_lock提供了,這就是必須使用unique_lock的原因;

3.使用細(xì)粒度鎖,盡量減小鎖的范圍,在notify_one()的時(shí)候,不需要處于互斥鎖的保護(hù)范圍內(nèi),所以在喚醒條件變量之前可以將鎖unlock()。

圖片

自旋鎖

假設(shè)我們有一個(gè)兩個(gè)處理器core1和core2計(jì)算機(jī),現(xiàn)在在這臺(tái)計(jì)算機(jī)上運(yùn)行的程序中有兩個(gè)線程:T1和T2分別在處理器core1和core2上運(yùn)行,兩個(gè)線程之間共享著一個(gè)資源。

首先我們說(shuō)明互斥鎖的工作原理,互斥鎖是是一種sleep-waiting的鎖。假設(shè)線程T1獲取互斥鎖并且正在core1上運(yùn)行時(shí),此時(shí)線程T2也想要獲取互斥鎖(pthread_mutex_lock),但是由于T1正在使用互斥鎖使得T2被阻塞。當(dāng)T2處于阻塞狀態(tài)時(shí),T2被放入到等待隊(duì)列中去,處理器core2會(huì)去處理其他任務(wù)而不必一直等待(忙等)。也就是說(shuō)處理器不會(huì)因?yàn)榫€程阻塞而空閑著,它去處理其他事務(wù)去了。

而自旋鎖就不同了,自旋鎖是一種busy-waiting的鎖。也就是說(shuō),如果T1正在使用自旋鎖,而T2也去申請(qǐng)這個(gè)自旋鎖,此時(shí)T2肯定得不到這個(gè)自旋鎖。與互斥鎖相反的是,此時(shí)運(yùn)行T2的處理器core2會(huì)一直不斷地循環(huán)檢查鎖是否可用(自旋鎖請(qǐng)求),直到獲取到這個(gè)自旋鎖為止。

從“自旋鎖”的名字也可以看出來(lái),如果一個(gè)線程想要獲取一個(gè)被使用的自旋鎖,那么它會(huì)一致占用CPU請(qǐng)求這個(gè)自旋鎖使得CPU不能去做其他的事情,直到獲取這個(gè)鎖為止,這就是“自旋”的含義。

當(dāng)發(fā)生阻塞時(shí),互斥鎖可以讓CPU去處理其他的任務(wù);而自旋鎖讓CPU一直不斷循環(huán)請(qǐng)求獲取這個(gè)鎖。通過(guò)兩個(gè)含義的對(duì)比可以我們知道“自旋鎖”是比較耗費(fèi)CPU的。

// 用戶(hù)空間用 atomic_flag 實(shí)現(xiàn)自旋互斥
#include
#include
#include
#include

std::atomic_flag lock = ATOMIC_FLAG_INIT;

void f(int n)
{
for (int cnt = 0; cnt < 100; ++cnt) {
while (lock.test_and_set(std::memory_order_acquire)) // 獲得鎖
; // 自旋
std::cout << "Output from thread " << n << 'n';
lock.clear(std::memory_order_release); // 釋放鎖
}
}

int main()
{
std::vector v;
for (int n = 0; n < 10; ++n) {
v.emplace_back(f, n);
}
for (auto& t : v) {
t.join();
}
}

說(shuō)明:atomic是C++標(biāo)準(zhǔn)程序庫(kù)中的一個(gè)頭文件,定義了C++11標(biāo)準(zhǔn)中的一些表示線程、并發(fā)控制時(shí)原子操作的類(lèi)與方法等。此頭文件主要聲明了兩大類(lèi)原子對(duì)象:std::atomic和std::atomic_flag。

1、atomic_flag類(lèi):是一種簡(jiǎn)單的原子布爾類(lèi)型,只支持兩種操作:test_and_set(flag=true)和clear(flag=false)。

2、std::atomic類(lèi)模板:std::atomic既不可復(fù)制亦不可移動(dòng)。atomic對(duì)int、char、bool等數(shù)據(jù)結(jié)構(gòu)進(jìn)行了原子性封裝,在多線程環(huán)境中,對(duì)std::atomic對(duì)象的訪問(wèn)不會(huì)造成競(jìng)爭(zhēng)-冒險(xiǎn)。利用std::atomic可實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)的無(wú)鎖設(shè)計(jì)。

所謂的原子操作,取的就是“原子是最小的、不可分割的最小個(gè)體”的意義,它表示在多個(gè)線程訪問(wèn)同一個(gè)全局資源的時(shí)候,能夠確保所有其他的線程都不在同一時(shí)間內(nèi)訪問(wèn)相同的資源。也就是他確保了在同一時(shí)刻只有唯一的線程對(duì)這個(gè)資源進(jìn)行訪問(wèn)。這有點(diǎn)類(lèi)似互斥對(duì)象對(duì)共享資源的訪問(wèn)的保護(hù),但是原子操作更加接近底層,因而效率更高。使用原子操作能大大的提高程序的運(yùn)行效率。

#include
#include
#include
#include
#include


std::atomic count(0);

void threadFun()
{
for (int i = 0; i < 10000; i++)
count++;
}

int main(void)
{
clock_t start_time = clock();

// 啟動(dòng)多個(gè)線程
std::vector threads;
for (int i = 0; i < 10; i++)
threads.push_back(std::thread(threadFun));
for (auto&thad : threads)
thad.join();

// 檢測(cè)count是否正確 10000*10 = 100000
std::cout << "count number:" << count << std::endl;

clock_t end_time = clock();
std::cout << "耗時(shí):" << end_time - start_time << "ms" << std::endl;

return 0;
}

讀寫(xiě)鎖

先看看互斥鎖,它只有兩個(gè)狀態(tài),要么是加鎖狀態(tài),要么是不加鎖狀態(tài)。假如現(xiàn)在一個(gè)線程a只是想讀一個(gè)共享變量 i,因?yàn)椴淮_定是否會(huì)有線程去寫(xiě)它,所以我們還是要對(duì)它進(jìn)行加鎖。但是這時(shí)又有一個(gè)線程b試圖去讀共享變量 i,發(fā)現(xiàn)被鎖定了,那么b不得不等到a釋放了鎖后才能獲得鎖并讀取 i 的值,但是兩個(gè)讀取操作即使是同時(shí)發(fā)生的,也并不會(huì)像寫(xiě)操作那樣造成競(jìng)爭(zhēng),因?yàn)樗鼈儾恍薷淖兞康闹怠K晕覀兤谕诙鄠€(gè)線程試圖讀取共享變量的時(shí)候,它們可以立刻獲取因?yàn)樽x而加的鎖,而不是需要等待前一個(gè)線程釋放。

讀寫(xiě)鎖可以解決上面的問(wèn)題。它提供了比互斥鎖更好的并行性。因?yàn)橐宰x模式加鎖后,當(dāng)有多個(gè)線程試圖再以讀模式加鎖時(shí),并不會(huì)造成這些線程阻塞在等待鎖的釋放上。

讀寫(xiě)鎖是多線程同步的另外一個(gè)機(jī)制。在一些程序中存在讀操作和寫(xiě)操作問(wèn)題,對(duì)某些資源的訪問(wèn)會(huì)存在兩種可能情況,一種情況是訪問(wèn)必須是排他的,就是獨(dú)占的意思,這種操作稱(chēng)作寫(xiě)操作,另外一種情況是訪問(wèn)方式是可以共享的,就是可以有多個(gè)線程同時(shí)去訪問(wèn)某個(gè)資源,這種操作稱(chēng)為讀操作。這個(gè)問(wèn)題模型是從對(duì)文件的讀寫(xiě)操作中引申出來(lái)的。把對(duì)資源的訪問(wèn)細(xì)分為讀和寫(xiě)兩種操作模式,這樣可以大大增加并發(fā)效率。讀寫(xiě)鎖比互斥鎖適用性更高,并行性也更高。

需要注意的是,這里只是說(shuō)并行效率比互斥高,并不是速度一定比互斥鎖快,讀寫(xiě)鎖更復(fù)雜,系統(tǒng)開(kāi)銷(xiāo)更大。并發(fā)性好對(duì)于用戶(hù)體驗(yàn)非常重要,假設(shè)互斥鎖需要0.5秒,使用讀寫(xiě)鎖需要0.8秒,在類(lèi)似學(xué)生管理系統(tǒng)的軟件中,可能90%的操作都是查詢(xún)操作。如果突然有20個(gè)查詢(xún)請(qǐng)求,使用的是互斥鎖,則最后的查詢(xún)請(qǐng)求被滿足需要10秒,估計(jì)沒(méi)人接收。使用讀寫(xiě)鎖時(shí),因?yàn)樽x鎖能多次獲得,所以20個(gè)請(qǐng)求中,每個(gè)請(qǐng)求都能在1秒左右被滿足,用戶(hù)體驗(yàn)好的多。

讀寫(xiě)鎖特點(diǎn)

1 如果一個(gè)線程用讀鎖鎖定了臨界區(qū),那么其他線程也可以用讀鎖來(lái)進(jìn)入臨界區(qū),這樣可以有多個(gè)線程并行操作。這個(gè)時(shí)候如果再用寫(xiě)鎖加鎖就會(huì)發(fā)生阻塞。寫(xiě)鎖請(qǐng)求阻塞后,后面繼續(xù)有讀鎖來(lái)請(qǐng)求時(shí),這些后來(lái)的讀鎖都將會(huì)被阻塞。這樣避免讀鎖長(zhǎng)期占有資源,防止寫(xiě)鎖饑餓。

2 如果一個(gè)線程用寫(xiě)鎖鎖住了臨界區(qū),那么其他線程無(wú)論是讀鎖還是寫(xiě)鎖都會(huì)發(fā)生阻塞。

頭文件:boost/thread/shared_mutex.cpp

類(lèi)型:boost::shared_lock

用法:你可以使用boost::shared_ mutex的實(shí)例來(lái)實(shí)現(xiàn)同步,而不是使用std::mutex的實(shí)例。對(duì)于更新操作,std::lock_guard< boost::shared _mutex>和 std::unique _lock< boost::shared _mutex>可用于鎖定,以取代相應(yīng)的std::mutex特化。這確保了獨(dú)占訪問(wèn),就像std::mutex那樣。那些不需要更新數(shù)據(jù)結(jié)構(gòu)的線程能夠轉(zhuǎn)而使用 boost::shared _lock< boost::shared _mutex>來(lái)獲得共享訪問(wèn)。這與std::unique _lock用起來(lái)正是相同的,除了多個(gè)線程在同一時(shí)間,同一boost::shared _mutex上可能會(huì)具有共享鎖。唯一的限制是,如果任意一個(gè)線程擁有一個(gè)共享鎖,試圖獲取獨(dú)占鎖的線程會(huì)被阻塞,知道其他線程全都撤回它們的鎖。同樣的,如果一個(gè)線程具有獨(dú)占鎖,其他線程都不能獲取共享鎖或獨(dú)占鎖,直到第一個(gè)線程撤回它的鎖。

簡(jiǎn)單的說(shuō):

shared_lock是read lock。被鎖后仍允許其他線程執(zhí)行同樣被shared_lock的代碼。這是一般做讀操作時(shí)的需要。

unique_lock是write lock。被鎖后不允許其他線程執(zhí)行被shared_lock或unique_lock的代碼。在寫(xiě)操作時(shí),一般用這個(gè),可以同時(shí)限制unique_lock的寫(xiě)和share_lock的讀。

遞歸鎖

std::recursive_mutex 與 std::mutex 一樣,也是一種可以被上鎖的對(duì)象,但是和 std::mutex 不同的是,std::recursive_mutex 允許同一個(gè)線程對(duì)互斥量多次上鎖(即遞歸上鎖),來(lái)獲得對(duì)互斥量對(duì)象的多層所有權(quán),std::recursive_mutex 釋放互斥量時(shí)需要調(diào)用與該鎖層次深度相同次數(shù)的 unlock(),可理解為 lock() 次數(shù)和 unlock() 次數(shù)相同,除此之外,std::recursive_mutex 的特性和 std::mutex 大致相同。

例如函數(shù)a需要獲取鎖mutex,函數(shù)b也需要獲取鎖mutex,同時(shí)函數(shù)a中還會(huì)調(diào)用函數(shù)b。如果使用std::mutex必然會(huì)造成死鎖。但是使用std::recursive_mutex就可以解決這個(gè)問(wèn)題。

1. C++中使用的鎖:mutex

鎖,是生活中應(yīng)用十分廣泛的一種工具。鎖的本質(zhì)屬性是為事物提供“訪問(wèn)保護(hù)”,例如:大門(mén)上的鎖,是為了保護(hù)房子免于不速之客的到訪;自行車(chē)的鎖,是為了保護(hù)自行車(chē)只有owner才可以使用;保險(xiǎn)柜上的鎖,是為了保護(hù)里面的合同和金錢(qián)等重要東西……

在c++等高級(jí)編程語(yǔ)言中,鎖也是用來(lái)提供“訪問(wèn)保護(hù)”的,不過(guò)被保護(hù)的東西不再是房子、自行車(chē)、金錢(qián),而是內(nèi)存中的各種變量。此外,計(jì)算機(jī)領(lǐng)域?qū)τ凇版i”有個(gè)響亮的名字——mutex(互斥量),學(xué)過(guò)操作系統(tǒng)的同學(xué)對(duì)這個(gè)名字肯定很熟悉。

Mutex,互斥量,就是互斥訪問(wèn)的量。這種東東只在多線程編程中起作用,在單線程程序中是沒(méi)有什么用處的。從c++11開(kāi)始,c++提供了std::mutex類(lèi)型,對(duì)于多線程的加鎖操作提供了很好的支持。下面看一個(gè)簡(jiǎn)單的例子,對(duì)于mutex形成一個(gè)直觀的認(rèn)識(shí)。

Demo1——無(wú)鎖的情況

假定有一個(gè)全局變量counter,啟動(dòng)兩個(gè)線程,每個(gè)都對(duì)該變量自增10000次,最后輸出該變量的值。在第一個(gè)demo中,我們不加鎖,代碼文件保存為:mutex_demo1_no_mutex.cpp

#include
#include
#include
#include
#include
#include

int counter = 0;
void increase(int time) {
for (int i = 0; i < time; i++) {
// 當(dāng)前線程休眠1毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1));
counter++;
}
}

int main(int argc, char** argv) {
std::thread t1(increase, 10000);
std::thread t2(increase, 10000);
t1.join();
t2.join();
std::cout << "counter:" << counter << std::endl;
return 0;
}

為了顯示多線程競(jìng)爭(zhēng)導(dǎo)致結(jié)果不正確的現(xiàn)象,在每次自增操作的時(shí)候都讓當(dāng)前線程休眠1毫秒

對(duì)應(yīng) CMakeLists.txt

# 聲明要求的 cmake 最低版本
cmake_minimum_required(VERSION 3.0.0)
# 聲明一個(gè) cmake 工程
project(HelloMutex)
# 設(shè)置編譯模式
set(CMAKE_BUILD_TYPE "Debug")
# 語(yǔ)法:add_executable( 程序名 源代碼文件 )
add_executable(${PROJECT_NAME} mutex_demo1_no_mutex.cpp)

if(WIN32)
set(PLATFROM_LIBS Ws2_32 mswsock iphlpapi ntdll)
else(WIN32)
set(PLATFROM_LIBS pthread ${CAMKE_DL_LIBS})
endif(WIN32)
# 將庫(kù)文件鏈接到可執(zhí)行程序上
target_link_libraries(${PROJECT_NAME} ${PLATFROM_LIBS})

如果沒(méi)有多線程編程的相關(guān)經(jīng)驗(yàn),我們可能想當(dāng)然的認(rèn)為最后的counter為20000,如果這樣想的話,那就大錯(cuò)特錯(cuò)了。下面是兩次實(shí)際運(yùn)行的結(jié)果:

[root@2d129aac5cc5 demo]# ./mutex_demo1_no_mutex
counter:19997
[root@2d129aac5cc5 demo]# ./mutex_demo1_no_mutex
counter:19996

出現(xiàn)上述情況的原因是:自增操作"counter++"不是原子操作,而是由多條匯編指令完成的。多個(gè)線程對(duì)同一個(gè)變量進(jìn)行讀寫(xiě)操作就會(huì)出現(xiàn)不可預(yù)期的操作。以上面的demo1作為例子:假定counter當(dāng)前值為10,線程1讀取到了10,線程2也讀取到了10,分別執(zhí)行自增操作,線程1和線程2分別將自增的結(jié)果寫(xiě)回counter,不管寫(xiě)入的順序如何,counter都會(huì)是11,但是線程1和線程2分別執(zhí)行了一次自增操作,我們期望的結(jié)果是12!!!!!

輪到mutex上場(chǎng)。

Demo2——加鎖的情況

定義一個(gè)std::mutex對(duì)象用于保護(hù)counter變量。對(duì)于任意一個(gè)線程,如果想訪問(wèn)counter,首先要進(jìn)行"加鎖"操作,如果加鎖成功,則進(jìn)行counter的讀寫(xiě),讀寫(xiě)操作完成后釋放鎖(重要!!!);如果“加鎖”不成功,則線程阻塞,直到加鎖成功。

#include
#include
#include
#include
#include
#include

int counter = 0;
std::mutex mtx; // 保護(hù)counter

void increase(int time) {
for (int i = 0; i < time; i++) {
mtx.lock();
// 當(dāng)前線程休眠1毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1));
counter++;
mtx.unlock();
}
}

int main(int argc, char** argv) {
std::thread t1(increase, 10000);
std::thread t2(increase, 10000);
t1.join();
t2.join();
std::cout << "counter:" << counter << std::endl;
return 0;
}

上述代碼保存文件為:mutex_demo2_with_mutex.cpp。先來(lái)看幾次運(yùn)行結(jié)果:

[root@2d129aac5cc5 demo]# ./mutex_demo2_with_mutex
counter:20000
[root@2d129aac5cc5 demo]# ./mutex_demo2_with_mutex
counter:20000
[root@2d129aac5cc5 demo]# ./mutex_demo2_with_mutex
counter:20000

這次運(yùn)行結(jié)果和我們預(yù)想的一致,原因就是“利用鎖來(lái)保護(hù)共享變量”,在這里共享變量就是counter(多個(gè)線程都能對(duì)其進(jìn)行訪問(wèn),所以就是共享變量啦)。

簡(jiǎn)單總結(jié)一些std::mutex:

  • 對(duì)于std::mutex對(duì)象,任意時(shí)刻最多允許一個(gè)線程對(duì)其進(jìn)行上鎖
  • mtx.lock():調(diào)用該函數(shù)的線程嘗試加鎖。如果上鎖不成功,即:其它線程已經(jīng)上鎖且未釋放,則當(dāng)前線程block。如果上鎖成功,則執(zhí)行后面的操作,操作完成后要調(diào)用mtx.unlock()釋放鎖,否則會(huì)導(dǎo)致死鎖的產(chǎn)生
  • mtx.unlock():釋放鎖
  • std::mutex還有一個(gè)操作:mtx.try_lock(),字面意思就是:“嘗試上鎖”,與mtx.lock()的不同點(diǎn)在于:如果上鎖不成功,當(dāng)前線程不阻塞。

2. lock_guard

雖然std::mutex可以對(duì)多線程編程中的共享變量提供保護(hù),但是直接使用std::mutex的情況并不多。因?yàn)閮H使用std::mutex有時(shí)候會(huì)發(fā)生死鎖。回到上邊的例子,考慮這樣一個(gè)情況:假設(shè)線程1上鎖成功,線程2上鎖等待。但是線程1上鎖成功后,拋出異常并退出,沒(méi)有來(lái)得及釋放鎖,導(dǎo)致線程2“永久的等待下去”(線程2:我的心在等待永遠(yuǎn)在等待……),此時(shí)就發(fā)生了死鎖。給一個(gè)發(fā)生死鎖的 :

Demo3——死鎖的情況(僅僅為了演示,不要這么寫(xiě)代碼哦)

為了捕捉拋出的異常,我們重新組織一下代碼,代碼保存為:mutex_demo3_dead_lock.cpp。

#include
#include
#include
#include
#include
#include

int counter = 0;
std::mutex mtx; // 保護(hù)counter

void increase_proxy(int time, int id) {
for (int i = 0; i < time; i++) {
mtx.lock();
// 線程1上鎖成功后,拋出異常:未釋放鎖
if (id == 1) {
throw std::runtime_error("throw excption....");
}
// 當(dāng)前線程休眠1毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1));
counter++;
mtx.unlock();
}
}

void increase(int time, int id) {
try {
increase_proxy(time, id);
}
catch (const std::exception& e){
std::cout << "id:" << id << ", " << e.what() << std::endl;
}
}

int main(int argc, char** argv) {
std::thread t1(increase, 10000, 1);
std::thread t2(increase, 10000, 2);
t1.join();
t2.join();
std::cout << "counter:" << counter << std::endl;
return 0;
}

執(zhí)行后,結(jié)果如下圖所示:

[root@2d129aac5cc5 demo]# ./mutex_demo3_dead_lock
id:1, throw excption....

程序并沒(méi)有退出,而是永遠(yuǎn)的“卡”在那里了,也就是發(fā)生了死鎖。

那么這種情況該怎么避免呢?這個(gè)時(shí)候就需要std::lock_guard登場(chǎng)了。std::lock_guard只有構(gòu)造函數(shù)和析構(gòu)函數(shù)。簡(jiǎn)單的來(lái)說(shuō):當(dāng)調(diào)用構(gòu)造函數(shù)時(shí),會(huì)自動(dòng)調(diào)用傳入的對(duì)象的lock()函數(shù),而當(dāng)調(diào)用析構(gòu)函數(shù)時(shí),自動(dòng)調(diào)用unlock()函數(shù)(這就是所謂的RAII,讀者可自行搜索)。我們修改一下demo3。

Demo4——避免死鎖,lock_guard

demo4保存為:mutex_demo4_lock_guard.cpp

#include
#include
#include
#include
#include
#include

int counter = 0;
std::mutex mtx; // 保護(hù)counter

void increase_proxy(int time, int id) {
for (int i = 0; i < time; i++) {
// std::lock_guard對(duì)象構(gòu)造時(shí),自動(dòng)調(diào)用mtx.lock()進(jìn)行上鎖
// std::lock_guard對(duì)象析構(gòu)時(shí),自動(dòng)調(diào)用mtx.unlock()釋放鎖
std::lock_guard lk(mtx);
// 線程1上鎖成功后,拋出異常:未釋放鎖
if (id == 1) {
throw std::runtime_error("throw excption....");
}
// 當(dāng)前線程休眠1毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1));
counter++;
}
}

void increase(int time, int id) {
try {
increase_proxy(time, id);
}
catch (const std::exception& e){
std::cout << "id:" << id << ", " << e.what() << std::endl;
}
}

int main(int argc, char** argv) {
std::thread t1(increase, 10000, 1);
std::thread t2(increase, 10000, 2);
t1.join();
t2.join();
std::cout << "counter:" << counter << std::endl;
return 0;
}

執(zhí)行上述代碼,結(jié)果為:

[root@2d129aac5cc5 demo]# ./mutex_demo4_lock_guard
id:1, throw excption....
counter:10000

結(jié)果符合預(yù)期。所以,推薦使用std::mutex和std::lock_guard搭配使用,避免死鎖的發(fā)生。

3. std::lock_guard的第二個(gè)構(gòu)造函數(shù)

實(shí)際上,std::lock_guard有兩個(gè)構(gòu)造函數(shù),具體的(參考:cppreference):

explicit lock_guard( mutex_type& m ); (1) (since C++11)
lock_guard( mutex_type& m, std::adopt_lock_t t ); (2) (since C++11)
lock_guard( const lock_guard& ) = delete; (3) (since C++11)

在demo4中我們使用了第1個(gè)構(gòu)造函數(shù),第3個(gè)為拷貝構(gòu)造函數(shù),定義為刪除函數(shù)。這里我們來(lái)重點(diǎn)說(shuō)一下第2個(gè)構(gòu)造函數(shù)。

第2個(gè)構(gòu)造函數(shù)有兩個(gè)參數(shù),其中第二個(gè)參數(shù)類(lèi)型為:std::adopt_lock_t。這個(gè)構(gòu)造函數(shù)假定:當(dāng)前線程已經(jīng)上鎖成功,所以不再調(diào)用lock()函數(shù),這里不再給出具體的例子。

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 參數(shù)
    +關(guān)注

    關(guān)注

    11

    文章

    1860

    瀏覽量

    32428
  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4346

    瀏覽量

    62977
  • C++
    C++
    +關(guān)注

    關(guān)注

    22

    文章

    2114

    瀏覽量

    73859
  • 線程
    +關(guān)注

    關(guān)注

    0

    文章

    505

    瀏覽量

    19758
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    CC++const的用法比較

    為0)。針對(duì)Cconst的上述局限性,C++作出了重大的改進(jìn)。在C++,可以使用const來(lái)定義常數(shù),因?yàn)閏onst在編譯器的控制范疇內(nèi)
    發(fā)表于 11-11 10:00

    C語(yǔ)言的數(shù)據(jù)基本類(lèi)型分為哪幾種

    今天閑著無(wú)聊把書(shū)翻看一遍,收獲頗豐。目錄1、數(shù)據(jù)類(lèi)型本類(lèi)型數(shù)據(jù)長(zhǎng)度強(qiáng)制類(lèi)型轉(zhuǎn)換bit強(qiáng)制類(lèi)型轉(zhuǎn)換2、變量類(lèi)型局部變量全局變量變量的存儲(chǔ)類(lèi)別
    發(fā)表于 02-25 06:35

    C++ 面向?qū)ο蠖?b class='flag-5'>線程編程下載

    C++ 面向?qū)ο蠖?b class='flag-5'>線程編程下載
    發(fā)表于 04-08 02:14 ?70次下載

    C++面向?qū)ο蠖?b class='flag-5'>線程編程 (pdf電子版)

    C++面向?qū)ο蠖?b class='flag-5'>線程編程共分13章,全面講解構(gòu)建多線程架構(gòu)與增量多線程編程技術(shù)。第1章介紹了
    發(fā)表于 09-25 09:39 ?0次下載

    C語(yǔ)言數(shù)據(jù)的基本類(lèi)型

    現(xiàn)在大家只需要學(xué)習(xí)一下基本類(lèi)型。其他三種類(lèi)型更適合在后續(xù)分享相關(guān)知識(shí)點(diǎn)時(shí)詳細(xì)介紹。
    的頭像 發(fā)表于 05-05 20:08 ?3551次閱讀

    C語(yǔ)言和C++的特點(diǎn)與用法詳細(xì)說(shuō)明

    本文檔的主要內(nèi)容詳細(xì)介紹的是C語(yǔ)言和C++的特點(diǎn)與用法詳細(xì)說(shuō)明。
    的頭像 發(fā)表于 12-26 10:58 ?4476次閱讀

    Vulkan API 基本類(lèi)型介紹

    Vulkan 基本類(lèi)型,Vulkan 開(kāi)發(fā)需要設(shè)計(jì)的類(lèi)型非常多,整理其基本類(lèi)型如下,主要包含設(shè)備、隊(duì)列、命令緩沖、隊(duì)列家族、渲染通,管線等……
    的頭像 發(fā)表于 02-12 16:19 ?1925次閱讀

    Vulkan API 基本類(lèi)型 小結(jié)

    Vulkan 基本類(lèi)型,Vulkan 開(kāi)發(fā)需要設(shè)計(jì)的類(lèi)型非常多,整理其基本類(lèi)型如下,主要包含設(shè)備、隊(duì)列、命令緩沖、隊(duì)列家族、渲染通,管線等……
    發(fā)表于 02-23 06:02 ?5次下載
    Vulkan API 基<b class='flag-5'>本類(lèi)型</b> 小結(jié)

    C++可移植性及多線程

    本系列是開(kāi)源書(shū)C++ Best Practises[1]的中文版,全書(shū)從工具、代碼風(fēng)格、安全性、可維護(hù)性、可移植性、多線程、性能、正確性等角度全面介紹了現(xiàn)代C++項(xiàng)目的最佳實(shí)踐。本文是該系列的第五篇。
    的頭像 發(fā)表于 10-10 10:26 ?1051次閱讀

    C++入門(mén)之表達(dá)式

    C++中提供了很多操作符且定義了什么時(shí)候可以用于操作基本類(lèi)型,其還允許我們定義用于操作class類(lèi)型的操作符,接下來(lái)幾篇文章將會(huì)介紹C++中用于基
    的頭像 發(fā)表于 03-17 13:55 ?849次閱讀

    C++入門(mén)之string

    前一篇文章我們已經(jīng)了解了C++的基本類(lèi)型C++還提供了很多抽象數(shù)據(jù)類(lèi)型,例如字符串string,string包含多個(gè)字符,以及可變長(zhǎng)度的
    的頭像 發(fā)表于 03-17 13:58 ?608次閱讀

    C++的引用和指針

    之前的文章我們已經(jīng)介紹了C++的基本類(lèi)型如int,bool和double等,除了基本類(lèi)型C++還有一些更復(fù)雜的數(shù)據(jù)
    的頭像 發(fā)表于 03-17 14:00 ?681次閱讀

    如何用C++實(shí)現(xiàn)一個(gè)線程池呢?

    C++線程池是一種多線程管理模型,把線程分成任務(wù)執(zhí)行和線程調(diào)度兩部分。
    發(fā)表于 06-08 14:53 ?1839次閱讀
    如何用<b class='flag-5'>C++</b>實(shí)現(xiàn)一個(gè)<b class='flag-5'>線程</b>池呢?

    javascript基本類(lèi)型有哪些

    JavaScript的基本類(lèi)型包括: 數(shù)字類(lèi)型(Number):表示數(shù)字,包括整數(shù)和浮點(diǎn)數(shù)。 字符串類(lèi)型(String):表示文本數(shù)據(jù)。 布爾類(lèi)型
    的頭像 發(fā)表于 11-16 10:32 ?551次閱讀

    C++實(shí)現(xiàn)類(lèi)似instanceof的方法

    函數(shù),可實(shí)際上C++沒(méi)有。但是別著急,其實(shí)C++中有兩種簡(jiǎn)單的方法可以實(shí)現(xiàn)類(lèi)似Java的instanceof的功能。 在 C++
    的頭像 發(fā)表于 07-18 10:16 ?673次閱讀
    <b class='flag-5'>C++</b><b class='flag-5'>中</b>實(shí)現(xiàn)類(lèi)似instanceof的方法
    主站蜘蛛池模板: 色婷婷六月丁香七月婷婷 | 欧美黄色一级视频 | 韩国三级理在线视频观看 | 欧美特级午夜一区二区三区 | 亚洲天堂一区二区三区 | 久久天天躁夜夜躁狠狠 | 日韩在线免费看网站 | 国产精品护士 | 国产超爽人人爽人人做 | 一区二区亚洲视频 | 国产一级特黄特色aa毛片 | 色多多官网 | 国内精品久久久久影院免费 | 永久免费mv网站入口 | 亚洲精品乱码久久久久久蜜桃图片 | 久久免费视频99 | 天天草综合网 | 天天草夜夜爽 | 亚洲性人人天天夜夜摸 | 手机在线黄色网址 | 99午夜高清在线视频在观看 | 亚洲一区二区三区精品视频 | 视频在线观看网站 | 免费一级牲交毛片 | 成人影院免费观看 | 国内精品一区二区在线观看 | 天天做天天操 | 一道精品一区二区三区 | 你懂的在线观看网站 | 黄色三级视频在线观看 | 欧美在线伊人 | 欧美ol丝袜高跟秘书在线播放 | 欧美zooz人禽交免费 | 老司机午夜永久在线观看 | 国产在线观看黄色 | 91精品国产91久久久久久青草 | 亚洲免费毛片 | 在线观看免费高清 | 在线观看免费观看 | 欧美97色| 福利视频999 |