C++11各種鎖的具體使用
Mutex(互斥鎖)
什么是互斥量(鎖)?
這樣比喻:?jiǎn)挝簧嫌幸慌_(tái)打印機(jī)(共享數(shù)據(jù)a),你要用打印機(jī)(線程1要操作數(shù)據(jù)a),同事老王也要用打印機(jī)(線程2也要操作數(shù)據(jù)a),但是打印機(jī)同一時(shí)間只能給一個(gè)人用,此時(shí),規(guī)定不管是誰(shuí),在用打印機(jī)之前都要向領(lǐng)導(dǎo)申請(qǐng)?jiān)S可證(lock),用完后再向領(lǐng)導(dǎo)歸還許可證(unlock),許可證總共只有一個(gè),沒(méi)有許可證的人就等著在用打印機(jī)的同事用完后才能申請(qǐng)?jiān)S可證(阻塞,線程1lock互斥量后其他線程就無(wú)法lock,只能等線程1unlock后,其他線程才能lock)。那么,打印機(jī)就是共享數(shù)據(jù),訪問(wèn)打印機(jī)的這段代碼就是臨界區(qū),這個(gè)必須互斥使用的許可證就是互斥量(鎖)。
互斥量是為了解決數(shù)據(jù)共享過(guò)程中可能存在的訪問(wèn)沖突的問(wèn)題。這里的互斥量保證了使用打印機(jī)這一過(guò)程不被打斷。
死鎖
多線程編程時(shí)要考慮多個(gè)線程同時(shí)訪問(wèn)共享資源所造成的問(wèn)題,因此可以通過(guò)加鎖解鎖來(lái)保證同一時(shí)刻只有一個(gè)線程能訪問(wèn)共享資源;使用鎖的時(shí)候要注意,不能出現(xiàn)死鎖的狀況;
死鎖就是多個(gè)線程爭(zhēng)奪共享資源導(dǎo)致每個(gè)線程都不能取得自己所需的全部資源,從而程序無(wú)法向下執(zhí)行。
產(chǎn)生死鎖的四個(gè)必要條件(面試考點(diǎn)):
互斥(資源同一時(shí)刻只能被一個(gè)進(jìn)程使用)請(qǐng)求并保持(進(jìn)程在請(qǐng)資源時(shí),不釋放自己已經(jīng)占有的資源)不剝奪(進(jìn)程已經(jīng)獲得的資源,在進(jìn)程使用完前,不能強(qiáng)制剝奪)循環(huán)等待(進(jìn)程間形成環(huán)狀的資源循環(huán)等待關(guān)系) 互斥量mutex
互斥量mutex就是互斥鎖,加鎖的資源支持互斥訪問(wèn)
直接操作 mutex,即直接調(diào)用 mutex 的 lock / unlock 函數(shù)
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
std::mutex g_mutex;
int g_count = 0;
void Counter() {
g_mutex.lock();
int i = ++g_count;
std::cout << "count: " << i << std::endl;
// 前面代碼如有異常,unlock 就調(diào)不到了。
g_mutex.unlock();
}
int main() {
const std::size_t SIZE = 4;
// 創(chuàng)建一組線程。
std::vector<std::thread> v;
v.reserve(SIZE);
for (std::size_t i = 0; i < SIZE; ++i) {
v.emplace_back(&Counter);
}
// 等待所有線程結(jié)束。
for (std::thread& t : v) {
t.join();
}
return 0;
}
lock_guard
使用 lock_guard 自動(dòng)加鎖、解鎖。原理是 RAII,和智能指針類似。
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
std::mutex g_mutex;
int g_count = 0;
void Counter() {
// lock_guard 在構(gòu)造函數(shù)里加鎖,在析構(gòu)函數(shù)里解鎖。
std::lock_guard<std::mutex> lock(g_mutex);
int i = ++g_count;
std::cout << "count: " << i << std::endl;
}
int main() {
const std::size_t SIZE = 4;
std::vector<std::thread> v;
v.reserve(SIZE);
for (std::size_t i = 0; i < SIZE; ++i) {
v.emplace_back(&Counter);
}
for (std::thread& t : v) {
t.join();
}
return 0;
}
unique_lock
使用 unique_lock 自動(dòng)加鎖、解鎖。
unique_lock 與 lock_guard 原理相同,但是提供了更多功能(比如可以結(jié)合條件變量使用)。
注意:mutex::scoped_lock 其實(shí)就是 unique_lock 的 typedef。
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
std::mutex g_mutex;
int g_count = 0;
void Counter() {
std::unique_lock<std::mutex> lock(g_mutex);
int i = ++g_count;
std::cout << "count: " << i << std::endl;
}
int main() {
const std::size_t SIZE = 4;
std::vector<std::thread> v;
v.reserve(SIZE);
for (std::size_t i = 0; i < SIZE; ++i) {
v.emplace_back(&Counter);
}
for (std::thread& t : v) {
t.join();
}
return 0;
}
std::recursive_mutex
就像互斥鎖(mutex)一樣,遞歸互斥鎖(recursive_mutex)是可鎖定的對(duì)象,但它允許同一線程獲得對(duì)互斥鎖對(duì)象的多級(jí)所有權(quán)(多次lock)。
這允許從已經(jīng)鎖定它的線程鎖定(或嘗試鎖定)互斥對(duì)象,從而獲得對(duì)互斥對(duì)象的新所有權(quán)級(jí)別:互斥對(duì)象實(shí)際上將保持對(duì)該線程的鎖定,直到調(diào)用其成員 unlock 的次數(shù)與此所有權(quán)級(jí)別的次數(shù)相同。
#include <iostream>
#include <thread>
#include <mutex>
std::recursive_mutex mtx;
void print_block (int n, char c) {
mtx.lock();
mtx.lock();
mtx.lock();
for (int i=0; i<n; ++i) { std::cout << c; }
std::cout << '\n';
mtx.unlock();
mtx.unlock();
mtx.unlock();
}
int main ()
{
std::thread th1 (print_block,50,'*');
std::thread th2 (print_block,50,'$');
th1.join();
th2.join();
return 0;
}
std::timed_mutex
定時(shí)互斥鎖是一個(gè)可時(shí)間鎖定的對(duì)象,旨在通知何時(shí)關(guān)鍵代碼需要獨(dú)占訪問(wèn),就像常規(guī)互斥鎖一樣,但還支持定時(shí)嘗試鎖定請(qǐng)求。
| lock | 調(diào)用線程將鎖定timed_mutex,并在必要時(shí)進(jìn)行阻塞(其行為與 mutex 完全相同) |
| try_lock | 嘗試鎖定 timed_mutex,而不進(jìn)行阻塞(其行為與互斥鎖完全相同) |
| try_lock_for | 嘗試鎖定 timed_mutex, 最多阻塞 rel_time 時(shí)間 |
| try_lock_until | 嘗試鎖定 timed_mutex,最多阻塞到 abs_time 時(shí)間點(diǎn) |
| unlock | 解鎖 timed_mutex,釋放對(duì)其的所有權(quán)(其行為與 mutex 相同) |
std::recursive_timed_mutex
遞歸定時(shí)互斥鎖將 recursive_timed 和 timed_mutex 的功能結(jié)合到一個(gè)類中:它既支持通過(guò)單個(gè)線程獲
取多個(gè)鎖定級(jí)別又支持定時(shí)的 try_lock 請(qǐng)求。
成員函數(shù)與 timed_mutex 相同。
once_flag、call_once使用
在多線程中,有一種場(chǎng)景是某個(gè)任務(wù)只需要執(zhí)行一次,可以用C++11中的std::call_once函數(shù)配合std::once_flag來(lái)實(shí)現(xiàn)。多個(gè)線程同時(shí)調(diào)用某個(gè)函數(shù),std::call_once可以保證多個(gè)線程對(duì)該函數(shù)只調(diào)用一次
實(shí)現(xiàn)線程安全的單例模式
//h文件
#pragma once
#include <thread>
#include <iostream>
#include <mutex>
#include <memory>
class Task
{
private:
Task();
public:
static Task* task;
static Task* getInstance();
void fun();
};
//cpp文件
Task* Task::task;
Task::Task()
{
std::cout << "構(gòu)造函數(shù)" << std::endl;
}
Task* Task::getInstance()
{
static std::once_flag flag;
std::call_once(flag, []
{
task = new Task();
});
return task;
}
void Task::fun()
{
std::cout << "hello world!"<< std::endl;
}
條件變量condition_variable:
需要#include<condition_variable>,該頭文件中包含了條件變量相關(guān)的類,其中包括std::condition_variable類
如何使用?std::condition_variable類搭配std::mutex類來(lái)使用,std::condition_variable對(duì)象(std::condition_variable cond;)的作用不是用來(lái)管理互斥量的,它的作用是用來(lái)同步線程,它的用法相當(dāng)于編程中常見(jiàn)的flag標(biāo)志(A、B兩個(gè)人約定flag=true為行動(dòng)號(hào)角,默認(rèn)flag為false,A不斷的檢查flag的值,只要B將flag修改為true,A就開(kāi)始行動(dòng))。
類比到std::condition_variable,A、B兩個(gè)人約定notify_one為行動(dòng)號(hào)角,A就等著(調(diào)用wait(),阻塞),只要B一調(diào)用notify_one,A就開(kāi)始行動(dòng)(不再阻塞)。
std::condition_variable的具體使用代碼實(shí)例可以參見(jiàn)文章中“生產(chǎn)者與消費(fèi)者問(wèn)題”章節(jié)。
wait(locker) :
wait函數(shù)需要傳入一個(gè)std::mutex(一般會(huì)傳入std::unique_lock對(duì)象),即上述的locker。wait函數(shù)會(huì)自動(dòng)調(diào)用 locker.unlock() 釋放鎖(因?yàn)樾枰尫沛i,所以要傳入mutex)并阻塞當(dāng)前線程,本線程釋放鎖使得其他的線程得以繼續(xù)競(jìng)爭(zhēng)鎖。一旦當(dāng)前線程獲得notify(通常是另外某個(gè)線程調(diào)用 notify_* 喚醒了當(dāng)前線程),wait() 函數(shù)此時(shí)再自動(dòng)調(diào)用 locker.lock()上鎖。
cond.notify_one(): 隨機(jī)喚醒一個(gè)等待的線程
cond.notify_all(): 喚醒所有等待的線程
condition_variable的wait
std::condition_variable::wait 有兩個(gè)重載:
void wait( std::unique_lock<std::mutex>& lock ); (1) (since C++11) template< class Predicate > void wait( std::unique_lock<std::mutex>& lock, Predicate pred ); (2) (since C++11)
void wait( std::unique_lockstd::mutex& lock )
先unlock之前獲得的mutex,然后阻塞當(dāng)前的執(zhí)行線程。把當(dāng)前線程添加到等待線程列表中,該線程會(huì)持續(xù) block 直到被 notify_all() 或 notify_one() 喚醒。被喚醒后,該thread會(huì)重新獲取mutex,獲取到mutex后執(zhí)行后面的動(dòng)作。
線程block時(shí)候也可能被意外或者錯(cuò)誤喚醒。
template< class Predicate > void wait( std::unique_lockstd::mutex& lock, Predicate pred );
該重載設(shè)置了第二個(gè)參數(shù) Predicate, 只有當(dāng)pred為false時(shí),wait才會(huì)阻塞當(dāng)前線程。蓋崇仔等同于下面:
該情況下,線程被喚醒后,先重新判斷pred的值。如果pred為false,則會(huì)釋放mutex并重新阻塞在wait。因此,該mutex必須有pred的權(quán)限。該重載消除了意外喚醒的影響。
#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <condition_variable>
#include <deque>
#include <chrono>
std::deque<int> q;
std::mutex mu;
std::condition_variable condi;
void function_1()
{
int count = 10;
while (count > 0)
{
std::unique_lock<std::mutex> locker(mu);
q.push_back(count);
locker.unlock();
condi.notify_one(); //通知一個(gè)等待線程激活 condi.notify_all()激活所有線程
count--;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
void function_2()
{
int data = 100;
while (data > 1)
{
std::unique_lock<std::mutex> locker(mu);
condi.wait(locker, //解鎖locker,并進(jìn)入休眠 收到notify時(shí)又重新加鎖
[]() { return !q.empty(); }); //如果q不為空 線程才會(huì)被激活
data = q.front();
q.pop_front();
locker.unlock();
std::cout << data << std::endl;
}
}
int main()
{
std::thread t1(function_1);
std::thread t2(function_2);
t1.join();
t2.join();
return 0;
}
std::shared_mutex
std::shared_mutex 是讀寫(xiě)鎖,提供兩種訪問(wèn)權(quán)限的控制:共享性(shared)和排他性(exclusive)。通過(guò)lock/try_lock獲取排他性訪問(wèn)權(quán)限,通過(guò)lock_shared/try_lock_shared獲取共享性訪問(wèn)權(quán)限。這樣的設(shè)置對(duì)于區(qū)分不同線程的讀寫(xiě)操作特別有用。shared_mutex是c++17中引入的,使用時(shí)需要注意編譯器版本。
#include <iostream>
#include <mutex> // For std::unique_lock
#include <shared_mutex>
#include <thread>
class ThreadSafeCounter {
public:
ThreadSafeCounter() = default;
// Multiple threads/readers can read the counter's value at the same time.
unsigned int get() const {
std::shared_lock lock(mutex_);
return value_;
}
// Only one thread/writer can increment/write the counter's value.
void increment() {
std::unique_lock lock(mutex_);
value_++;
}
// Only one thread/writer can reset/write the counter's value.
void reset() {
std::unique_lock lock(mutex_);
value_ = 0;
}
private:
mutable std::shared_mutex mutex_;
unsigned int value_ = 0;
};
int main() {
ThreadSafeCounter counter;
auto increment_and_print = [&counter]() {
for (int i = 0; i < 3; i++) {
counter.increment();
std::cout << std::this_thread::get_id() << ' ' << counter.get() << '\n';
// Note: Writing to std::cout actually needs to be synchronized as well
// by another std::mutex. This has been omitted to keep the example small.
}
};
std::thread thread1(increment_and_print);
std::thread thread2(increment_and_print);
thread1.join();
thread2.join();
}
原子操作
所謂原子操作,就是多線程程序中“最小的且不可并行化的”操作。對(duì)于在多個(gè)線程間共享的一個(gè)資源而言,這意味著同一時(shí)刻,多個(gè)線程中有且僅有一個(gè)線程在對(duì)這個(gè)資源進(jìn)行操作,即互斥訪問(wèn)。提到“互斥”訪問(wèn),熟悉多線程開(kāi)發(fā)的同學(xué)可能立即想到Windows平臺(tái)下使用的臨界區(qū)/CRITICAL_SECTION、互斥體/Mutex。實(shí)現(xiàn)互斥通常需要平臺(tái)相關(guān)的特殊指令,在C++11標(biāo)準(zhǔn)之前,這意味著需要在C/C++代碼中嵌入平臺(tái)相關(guān)的內(nèi)聯(lián)匯編代碼。 平臺(tái)相關(guān)意味著:1.你必須了解平臺(tái)相關(guān)的編譯器擴(kuò)展;2.無(wú)法跨平臺(tái)運(yùn)行你的多線程程序。
多線程中需要同步的總是資源/數(shù)據(jù),而不是代碼。因此C++11對(duì)數(shù)據(jù)進(jìn)行了更為良好的抽象,引入"原子數(shù)據(jù)類型"/atomic類型,以達(dá)到對(duì)開(kāi)發(fā)者掩蓋互斥鎖、臨界區(qū)的目的。要知道,這些臨界區(qū)、互斥鎖才是平臺(tái)相關(guān)的東西。來(lái)看下面的示例代碼。
#include<atomic>
#include<thread>
#include<iostream>
using namespace std;
std::atomic_llong total{ 0 };//原子數(shù)據(jù)類型
void func(int)
{
for (long long i = 0; i<100000000LL; ++i)
{
total += i;
}
}
int main()
{
thread t1(func, 0);
thread t2(func, 0);
t1.join();
t2.join();
cout<<total<<endl;//9999999900000000
return 0;
}
原子數(shù)據(jù)類型/atomic類型
atomic模板類
template <class T> struct atomic
//example
#include<atomic>
void test()
{
std::atomic_int nThreadData; // std::atomic_int <----> std::atomic<int>
nThreadData = 10;
nThreadData.store(10);
//TODO: use nThreadData here;
}
對(duì)于內(nèi)置型數(shù)據(jù)類型,C11和C++11標(biāo)準(zhǔn)中都已經(jīng)提供了實(shí)例化原子類型,如下表所示:

atomic類型原子操作接口如下

到此這篇關(guān)于C++11各種鎖的具體使用的文章就介紹到這了,更多相關(guān)C++11各種鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言進(jìn)階練習(xí)二叉樹(shù)的遞歸遍歷
樹(shù)是一種重要的非線性數(shù)據(jù)結(jié)構(gòu),直觀地看,它是數(shù)據(jù)元素(在樹(shù)中稱為結(jié)點(diǎn))按分支關(guān)系組織起來(lái)的結(jié)構(gòu),很象自然界中的樹(shù)那樣。樹(shù)結(jié)構(gòu)在客觀世界中廣泛存在,如人類社會(huì)的族譜和各種社會(huì)組織機(jī)構(gòu)都可用樹(shù)形象表示,本篇介紹二叉樹(shù)的遞歸與非遞歸遍歷的方法2022-06-06
C++?boost?thread庫(kù)用法詳細(xì)講解
Boost是為C++語(yǔ)言標(biāo)準(zhǔn)庫(kù)提供擴(kuò)展的一些C++程序庫(kù)的總稱。Boost庫(kù)是一個(gè)可移植、提供源代碼的C++庫(kù),作為標(biāo)準(zhǔn)庫(kù)的后備,是C++標(biāo)準(zhǔn)化進(jìn)程的開(kāi)發(fā)引擎之一,是為C++語(yǔ)言標(biāo)準(zhǔn)庫(kù)提供擴(kuò)展的一些C++程序庫(kù)的總稱2022-11-11
C語(yǔ)言近萬(wàn)字為你講透樹(shù)與二叉樹(shù)
樹(shù)是計(jì)算機(jī)算法最重要的非線性結(jié)構(gòu)。因?yàn)闃?shù)能很好地描述結(jié)構(gòu)的分支關(guān)系和層次特性,所以在計(jì)算機(jī)科學(xué)和計(jì)算機(jī)應(yīng)用領(lǐng)域有著廣泛的應(yīng)用。這篇文章我就帶大家一起了解一下樹(shù)、二叉樹(shù)這種結(jié)構(gòu),下篇文章會(huì)重點(diǎn)向大家介紹二叉樹(shù)的遍歷算法2022-05-05
C++在C語(yǔ)言基礎(chǔ)之上增強(qiáng)的幾個(gè)實(shí)用特性總結(jié)
這篇文章主要介紹了C++在C語(yǔ)言基礎(chǔ)之上增強(qiáng)的幾個(gè)實(shí)用特性總結(jié),包括C++中更強(qiáng)的類型約束以及結(jié)構(gòu)體方面等一些更加高級(jí)的特性,需要的朋友可以參考下2016-03-03
C++內(nèi)存泄漏檢測(cè)和解決方法小結(jié)
內(nèi)存泄露在編程中是常見(jiàn)的一種問(wèn)題,一但程序發(fā)生內(nèi)存泄露問(wèn)題,將導(dǎo)致程序崩潰無(wú)法運(yùn)行,內(nèi)存泄漏是指程序在運(yùn)行過(guò)程中,由于疏忽或錯(cuò)誤導(dǎo)致已分配的內(nèi)存空間無(wú)法被正確釋放,本文給大家就介紹了C++中內(nèi)存泄漏如何檢測(cè)和解決,需要的朋友可以參考下2025-01-01
C語(yǔ)言解讀數(shù)組循環(huán)右移問(wèn)題
這篇文章主要介紹了C語(yǔ)言解讀數(shù)組循環(huán)右移問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
怎么實(shí)現(xiàn)類的成員函數(shù)作為回調(diào)函數(shù)
不使用成員函數(shù),為了訪問(wèn)類的成員變量,可以使用友元操作符(friend),在C++中將該函數(shù)說(shuō)明為類的友元即可2013-10-10

