自己做的網(wǎng)站能上傳嗎公司網(wǎng)站推廣技巧
前言:
- 在本期,我將給大家介紹的是 C++11 中新引進的知識,即關(guān)于線程庫的相關(guān)知識。
目錄
(一)線程庫的介紹
1、線程庫的由來
2、線程庫的簡單介紹
(二)線程函數(shù)參數(shù)
(三)原子性操作庫
(四)lock_guard與unique_lock
1、mutex的種類
2、lock_guard
3、unique_lock
(五)condition_variable
總結(jié)
(一)線程庫的介紹
1、線程庫的由來
C++11引入線程庫的主要原因是滿足多核處理器和并行計算的需求。在現(xiàn)代計算機體系結(jié)構(gòu)中,多核處理器已成為主流,而同時執(zhí)行多個任務和利用多核處理器的能力對于實現(xiàn)高性能和并行計算至關(guān)重要。在C++11之前,涉及到多線程問題,都是和平臺相關(guān)的,比如windows和linux下各有自己的接口,這使得代碼的可移植性比較差。
C++11引入了線程庫作為標準庫的一部分,以提供一種標準化的、平臺無關(guān)的方法來處理多線程和并發(fā)編程。它的目標是提供一組類和函數(shù),為程序員提供創(chuàng)建、管理和同步線程的工具,并且以一種可移植和可靠的方式工作。
C++11線程庫的引入使得多線程和并發(fā)編程在C++中成為一項原生支持的特性,為開發(fā)者提供了更好的工具和能力來利用多核處理器和實現(xiàn)高效的并行計算。
2、線程庫的簡單介紹
C++11引入了一個標準庫,即C++標準線程庫(C++ Standard Thread Library),它允許程序員在C++中輕松地創(chuàng)建和管理線程。這個線程庫提供了一組類和函數(shù),用于創(chuàng)建、管理和同步線程的操作。
以下是C++11提供的線程庫的主要組件:
-
std::thread:這個類代表一個執(zhí)行線程。可以通過傳遞一個可調(diào)用對象(函數(shù)、函數(shù)指針、lambda表達式等)和它的參數(shù)來創(chuàng)建線程。線程會在構(gòu)造函數(shù)中啟動,并在可調(diào)用對象執(zhí)行完畢時結(jié)束。還可以使用成員函數(shù)來獲取和設置線程的屬性,如標識符、狀態(tài)等。
-
std::mutex:這個類實現(xiàn)了互斥鎖(mutex),用于線程間的互斥訪問共享數(shù)據(jù)。通過使用互斥鎖,可以確保在某個線程訪問共享資源時,其他線程無法同時訪問它,以避免數(shù)據(jù)競爭和不一致性。
-
std::condition_variable:這個類提供了條件變量,用于在線程之間進行同步和通信。條件變量允許一個或多個線程等待某個條件滿足,并在條件滿足時被喚醒。通常與互斥鎖一起使用,以實現(xiàn)線程間的順序執(zhí)行和通信。
-
std::atomic:這個類提供了原子操作(atomic operations),用于在多線程環(huán)境中對共享數(shù)據(jù)進行原子訪問。原子操作是不可中斷的,可以確保操作的完整性和一致性,避免競爭條件和數(shù)據(jù)沖突。
要使用標準庫中的線程,必須包含< thread >頭文件:
?
?
以下是一些說明事項:
1、線程是操作系統(tǒng)中的一個概念,線程對象可以關(guān)聯(lián)一個線程,用來控制線程以及獲取線程的
狀態(tài)。
?
// 線程函數(shù),打印一條消息
void Print()
{cout << "Hello from thread!" <<endl;
}int main()
{// 創(chuàng)建一個線程對象,并傳遞線程函數(shù)作為可調(diào)用對象thread t1(Print);// 判斷線程是否可執(zhí)行if (t1.joinable()){cout << "Thread is joinable." << endl;}else {cout << "Thread is not joinable." << endl;}// 等待線程執(zhí)行完畢t1.join();// 判斷線程是否仍然可執(zhí)行if (t1.joinable()){cout << "Thread is joinable." << endl;}else {cout << "Thread is not joinable." << endl;}return 0;
}
輸出展示:
?
?
【解釋說明】
- 在這個示例中,我們定義了一個
printMessage()
函數(shù)作為線程函數(shù),它會打印一條消息。在main()
函數(shù)中,我們創(chuàng)建了 t1,并傳遞了線程函數(shù)printMessage
作為可調(diào)用對象。 - 接下來,我們通過?joinable()?成員函數(shù)判斷線程對象是否可執(zhí)行。在創(chuàng)建線程后但尚未調(diào)用
join()
函數(shù)之前,線程是可執(zhí)行的。在調(diào)用join()
函數(shù)后,線程會等待線程函數(shù)執(zhí)行完畢以后才結(jié)束,并且線程對象不再可執(zhí)行。 - 最后,我們再次使用?joinable()?成員函數(shù)來判斷線程對象是否仍然可執(zhí)行。在
join()
函數(shù)調(diào)用之后,線程對象不再可執(zhí)行,可以安全地銷毀線程對象。
2、當創(chuàng)建一個線程對象后,沒有提供線程函數(shù),該對象實際沒有對應任何線程
int main()
{// 創(chuàng)建一個空的線程對象thread t2;// 判斷線程是否可執(zhí)行if (t2.joinable()){cout << "Thread is joinable." << endl;}else {cout << "Thread is not joinable." << endl;}// 銷毀線程對象return 0;
}
【解釋說明】
- 在這個示例中,我們創(chuàng)建了一個空的線程對象 t2?,沒有傳遞任何線程函數(shù)作為可調(diào)用對象。在創(chuàng)建后,t2?并沒有關(guān)聯(lián)到任何線程執(zhí)行代碼。
- 然后,我們使用 joinable()成員函數(shù)來判斷線程對象是否可執(zhí)行。由于沒有提供線程函數(shù),線程對象并沒有對應的線程,因此它不可執(zhí)行。
3、get_id()的返回值類型為id類型,id類型實際為std::thread命名空間下封裝的一個類,該類中
包含了一個結(jié)構(gòu)體:
// vs下查看
typedef struct
{ /* thread identifier for Win32 */void *_Hnd; /* Win32 HANDLE */unsigned int _Id;
} _Thrd_imp_t;
接下來我們簡單的看以下代碼:
void threadFunction()
{cout << "Thread ID: " << std::this_thread::get_id() << endl;
}int main()
{thread t1(threadFunction);thread t2(threadFunction);cout << "Main thread ID: " << std::this_thread::get_id() << endl;if (t1.get_id() == t2.get_id()) {cout << "t1 and t2 have the same thread ID." <<endl;}else {cout << "t1 and t2 have different thread IDs." << endl;}t1.join();t2.join();return 0;
}
?【解釋說明】?
- 創(chuàng)建了兩個對象
t1
和t2
,它們分別關(guān)聯(lián)到一個線程函數(shù)threadFunction
。在threadFunction
函數(shù)中,我們打印線程的唯一標識符。 - 緊接著在主函數(shù)中,我們首先打印主線程的唯一標識符。然后,我們通過
get_id()
函數(shù)分別獲取t1
和t2
的線程ID,并使用比較操作符對它們進行比較。
?
輸出展示:
通過比較線程ID,我們可以判斷不同線程對象是否關(guān)聯(lián)到同一個線程,這在多線程編程中非常有用,可以幫助我們進行線程間的協(xié)調(diào)和控制。
3、當創(chuàng)建一個線程對象后,并且給線程關(guān)聯(lián)線程函數(shù),該線程就被啟動,與主線程一起運行。
線程函數(shù)一般情況下可按照以下三種方式提供:
- 函數(shù)指針
- lambda表達式
- 函數(shù)對象
💨 接下來,我們分別敘述各個方式:
- ①函數(shù)指針
#include <iostream>
#include <thread>void ThreadFunc(int a) {cout << "Thread: " <<":"<< a << endl;
}int main() {thread t1(ThreadFunc, 10);t1.join();return 0;
}
輸出展示:
?【解釋說明】
- 上述代碼使用函數(shù)指針方式提供了線程函數(shù),并成功創(chuàng)建了一個線程對象,使其在后臺執(zhí)行了
ThreadFunc
函數(shù); - 使用函數(shù)指針方式創(chuàng)建了一個線程對象
t1
,并將ThreadFunc
函數(shù)及參數(shù)10
傳遞給它
?
- ②lambda表達式
int main()
{int threadArg = 10;// 創(chuàng)建線程對象,并傳遞lambda表達式作為線程函數(shù)thread t2([threadArg]() {// 在lambda表達式中執(zhí)行線程函數(shù)的邏輯cout << "Thread Function: Value = " << threadArg << endl;});// 做一些其他的操作...// 等待線程執(zhí)行完畢t2.join();return 0;
}
輸出展示:
?
?【解釋說明】
- 在這個示例中,我們定義了一個整型變量 threadArg 作為線程函數(shù)的參數(shù)。
- 然后,我們創(chuàng)建了一個線程對象 t2 ,并通過lambda表達式來定義線程函數(shù)。lambda表達式接收?threadArg 作為捕獲變量,并在其中輸出線程函數(shù)的邏輯。
- 然后調(diào)用
join()
函數(shù)等待線程執(zhí)行完畢。 - 這樣,就通過lambda表達式方式提供了線程函數(shù),并使用創(chuàng)建的線程對象執(zhí)行了該線程函數(shù)。
- ③函數(shù)對象
class TF
{
public:void operator()(){cout << "Thread3" << endl;}
};int main()
{// 線程函數(shù)為函數(shù)對象TF tf;thread t3(tf);t3.join();return 0;
}
輸出展示:
?【解釋說明】
- 上述代碼定義了一個名為
TF
的類,重載了函數(shù)調(diào)用運算符operator()
作為線程函數(shù)的實現(xiàn); - 代碼創(chuàng)建了一個名為
tf
的TF
類的對象,并將其作為線程函數(shù)對象傳遞給了thread對象t3
的構(gòu)造函數(shù),然后使用t3.join()
等待線程執(zhí)行完畢; - 整體來說,上述代碼使用函數(shù)對象方式提供了線程函數(shù),并成功創(chuàng)建了一個線程對象,使其在后臺執(zhí)行了函數(shù)對象
TF
的調(diào)用運算符重載函數(shù)。
4、thread類是防拷貝的,不允許拷貝構(gòu)造以及賦值,但是可以移動構(gòu)造和移動賦值,即將一個線程對象關(guān)聯(lián)線程的狀態(tài)轉(zhuǎn)移給其他線程對象,轉(zhuǎn)移期間不意向線程的執(zhí)行。
下面是一個示例代碼演示了如何使用移動語義操作來轉(zhuǎn)移線程對象:
void threadFunction()
{cout << "Thread Function" << endl;
}int main()
{thread t1(threadFunction);// 移動構(gòu)造一個新的線程對象thread t2 = move(t1);// 線程對象t1不再與任何線程關(guān)聯(lián)// t1 現(xiàn)在為空,不能再在其上執(zhí)行join()或其他線程相關(guān)操作// 等待線程執(zhí)行完畢t2.join();return 0;
}
【輸出展示】
?【解釋說明】
- 上述代碼中我們創(chuàng)建了一個線程對象
t1
,關(guān)聯(lián)了線程函數(shù)threadFunction(),
然后,我們使用移動構(gòu)造函數(shù)?move()
將t1
的所有權(quán)轉(zhuǎn)移到t2
上。此時,t1
變?yōu)榭?#xff0c;不再與任何線程關(guān)聯(lián)。 - 最后,我們調(diào)用
t2.join()
等待t2
關(guān)聯(lián)的線程執(zhí)行完畢。 - 通過移動語義操作,我們可以將線程對象的所有權(quán)傳遞給其他線程對象,而無需執(zhí)行線程的實際執(zhí)行。這種方式允許資源的有效轉(zhuǎn)移,并提供了更靈活的線程管理和編程方式。
?5、可以通過jionable()函數(shù)判斷線程是否是有效的,如果是以下任意情況,則線程無效
- 采用無參構(gòu)造函數(shù)構(gòu)造的線程對象
- 線程對象的狀態(tài)已經(jīng)轉(zhuǎn)移給其他線程對象
- 線程已經(jīng)調(diào)用jion或者detach結(jié)束
?
(二)線程函數(shù)參數(shù)
接下來,我們直接通過代碼來進行觀察現(xiàn)象,最后在進行總結(jié):
現(xiàn)有以下代碼:
void ThreadFunc1(int x)
{x += 10;
}int main()
{int a = 10;// 在線程函數(shù)中對a修改,不會影響外部實參,//因為:線程函數(shù)參數(shù)雖然是引用方式,但其實際引用的是線程棧中的拷貝thread t1(ThreadFunc1, a);t1.join();cout << a << endl;return 0;
}
輸出展示:
?【解釋說明】
- 線程函數(shù)? ThreadFunc1?的參數(shù)是以傳值的方式傳遞的,但是在對?
x
?進行修改時,并不會影響到外部的實參?a,
盡管?ThreadFunc1?中對?x
?進行了修改(x += 10
),但輸出結(jié)果仍然會是?10
,而不是?20
。 - 這是因為在創(chuàng)建線程對象時,使用?
a
?的值初始化了?ThreadFunc1?的參數(shù)?x
?的副本。線程持有這個副本并在單獨的執(zhí)行線程中執(zhí)行相應代碼。修改副本?x
?不會影響到原始變量?a
。
但是如果我就想要在線程函數(shù)中修改外部實參 a?
的值,你可以將?std::ref()?作為參數(shù)傳遞給線程對象的構(gòu)造函數(shù),以便引用原始變量。
修改后的代碼示例如下:
void ThreadFunc1(int& x)
{x += 10;
}int main()
{int a = 10;// 如果想要通過形參改變外部實參時,必須借助std::ref()函數(shù)thread t2(ThreadFunc1, ref(a));t2.join();cout << a << endl;return 0;
}
輸出展示:
?這樣,參數(shù)?x
?就是對原始變量?a
?的引用,對其進行的修改將反映在主線程中的原始變量?a
,輸出結(jié)果將是?20.
?
其次,對于指針也有著一樣的效果,具體如下:
void ThreadFunc2(int* x)
{*x += 10;
}
int main()
{int a = 10;// 地址的拷貝thread t3(ThreadFunc2, &a);t3.join();cout << a << endl;return 0;
}
輸出展示:
?【解釋說明】
- 在 ThreadFunc2 中可以通過解引用指針?
x
?并對其進行修改來改變原始變量?a
?的值。最終,輸出?a
?的值將是原始值?10
?加上?10
,即?20。
【注意】
如果是類成員函數(shù)作為線程參數(shù)時,必須將this作為線程函數(shù)參數(shù)
- 這是因為成員函數(shù)需要訪問類的成員變量和其他成員函數(shù),并且需要通過對象的指針(
this
?指針)來進行訪問。
代碼展示:
class Func
{
public:void threadFunc(int a) {cout << "Thread function: " << a << endl;}
};int main()
{Func obj;int value = 10;thread t(&Func::threadFunc, &obj, value);t.join();return 0;
}
?【解釋說明】
- 創(chuàng)建 thread 對象時,我們傳遞了 &obj?,即對象的指針作為額外的參數(shù)。這樣,線程函數(shù)在執(zhí)行時將可訪問到類的成員變量和其他成員函數(shù)。
- 通過這種方式,我們可以在類的成員函數(shù)中直接操作對象的狀態(tài),并且可以與其他線程安全地共享該對象。
【小結(jié)】
線程函數(shù)的參數(shù)是以值拷貝的方式拷貝到線程??臻g中的,因此:即使線程參數(shù)為引用類型,在線程中修改后也不能修改外部實參,因為其實際引用的是線程棧中的拷貝,而不是外部實參。
?
(三)原子性操作庫
多線程最主要的問題是共享數(shù)據(jù)帶來的問題(即線程安全)。如果共享數(shù)據(jù)都是只讀的,那么沒問題,因為只讀操作不會影響到數(shù)據(jù),更不會涉及對數(shù)據(jù)的修改,所以所有線程都會獲得同樣的數(shù)據(jù)。但是,當一個或多個線程要修改共享數(shù)據(jù)時,就會產(chǎn)生很多潛在的麻煩。比如:
?
int sum = 0;
void fun(size_t num)
{for (int i = 0; i < num; ++i){++sum;}
}
int main()
{cout << "Before joining,sum = " << sum << endl;thread t1(fun, 1000000);thread t2(fun, 1000000);t1.join();t2.join();cout << "After joining,sum = " << sum << endl;return 0;
}
【解釋說明】
- 大家第一看能看出來上述代碼有什么問題嗎?我相信各位聰明的小伙伴都已經(jīng)知道了。主要的問題在于多個線程同時訪問和修改全局變量 sum,這可能導致數(shù)據(jù)競爭問題。
輸出展示:
?【解釋說明】
- ?我們通過連續(xù)幾次的輸出可以發(fā)現(xiàn),每次輸出的結(jié)果都不一致。因此,不難看出此處就引起了相應的線程安全問題。
?接下來,我們通過嘗試打印 sum和num 的地址查看是否相同:
??【解釋說明】
- 由于 sum?是一個全局變量,它在內(nèi)存中具有唯一的地址;
- 但是, num?是在每次調(diào)用
fun
函數(shù)時創(chuàng)建的局部變量,每個線程都有自己的num變量。因此,輸出&
num將顯示每個線程的num變量的地址。 - 在多線程環(huán)境中,每個線程都擁有自己的??臻g,因此它們的局部變量是獨立的。然而,全局變量是共享的,多個線程可以同時訪問和修改它。
為了避免數(shù)據(jù)競爭問題,在C++98中傳統(tǒng)的解決方式為:可以使用鎖來保護對sum?的訪問
?下面是修改后的代碼示例:
mutex mtx;
int sum = 0;void fun(int n)
{for (int i = 0; i < n; i++){mtx.lock();++sum;mtx.unlock();}
}int main()
{cout << "Before joining,sum = " << sum << endl;thread t1(fun, 10000);thread t2(fun, 10000);t1.join();t2.join();cout << "After joining,sum = " << sum << endl;return 0;
}
?輸出展示:
?上述代碼的加鎖方式屬于并行加鎖的方式,我們還可以進行串行加鎖:
void fun(int n)
{//串行mtx.lock();for (int i = 0; i < n; i++){++sum;}mtx.unlock();}
我們分別對上述兩種方式進行簡單的測試,看最終的效率如何:
- ?串行方式下:
- 并行方式下:
?
?
【小結(jié)】
- 串行方式?jīng)]有線程同步的開銷,因此在沒有并發(fā)需求的情況下,串行方式可能會更快?
雖然加鎖可以解決,但是加鎖有一個缺陷就是:只要一個線程在對sum++時,其他線程就會被阻塞,會影響程序運行的效率,而且鎖如果控制不好,還容易造成死鎖。
?
因此C++11中引入了原子操作。所謂原子操作:即不可被中斷的一個或一系列操作,C++11引入的原子操作類型,使得線程間數(shù)據(jù)的同步變得非常高效。
?
- ?特別需要注意的一點的要使用以上原子操作變量時,必須添加頭文件<atomic>
atomic<int> sum={ 0 };
void fun(int num)
{for (int i = 0; i < num; ++i){++sum; // 原子操作}
}int main()
{cout << "Before joining, sum = " << sum << std::endl;thread t1(fun, 1000000);thread t2(fun, 1000000);t1.join();t2.join();cout << "After joining, sum = " << sum << std::endl;return 0;
}
在C++11中,程序員不需要對原子類型變量進行加鎖解鎖操作,線程能夠?qū)υ宇愋妥兞炕コ獾脑L問。
更為普遍的,程序員可以使用atomic類模板,定義出需要的任意原子類型。
?
atmoic<T> t; // 聲明一個類型為T的原子類型變量t
注意:原子類型通常屬于"資源型"數(shù)據(jù),多個線程只能訪問單個原子類型的拷貝,因此在C++11中,原子類型只能從其模板參數(shù)中進行構(gòu)造,不允許原子類型進行拷貝構(gòu)造、移動構(gòu)造以及operator=等,為了防止意外,標準庫已經(jīng)將atmoic模板類中的拷貝構(gòu)造、移動構(gòu)造、賦值運算符重載默認刪除掉了。
?
int main()
{atomic<int> a1(0);//atomic<int> a2(a1); // 編譯失敗atomic<int> a2(0);//a2 = a1; // 編譯失敗return 0;
}
輸出展示:
?
?
(四)lock_guard與unique_lock
在多線程環(huán)境下,如果想要保證某個變量的安全性,只要將其設置成對應的原子類型即可,即高效又不容易出現(xiàn)死鎖問題。但是有些情況下,我們可能需要保證一段代碼的安全性,那么就只能通過鎖的方式來進行控制。
?
lock_guard 和?unique_lock 都是C++中的互斥鎖封裝類,用于在多線程環(huán)境中實現(xiàn)線程的同步和互斥訪問。
- 比如:一個線程對變量number進行加一100次,另外一個減一100次,每次操作加一或者減一之后,輸出number的結(jié)果,要求:number最后的值為1
int number = 0;
mutex mtx;
int ThreadProc1()
{for (int i = 0; i < 100; i++){mtx.lock();++number;cout << "thread 1 :" << number << endl;mtx.unlock();}return 0;
}int ThreadProc2()
{for (int i = 0; i < 100; i++){mtx.lock();--number;cout << "thread 2 :" << number << endl;mtx.unlock();}return 0;
}int main()
{thread t1(ThreadProc1);thread t2(ThreadProc2);t1.join();t2.join();cout << "number:" << number << endl;system("pause");return 0;
}
【解釋說明】
- 上述代碼的缺陷:鎖控制不好時,可能會造成死鎖,最常見的比如在鎖中間代碼返回,或者在鎖的范圍內(nèi)拋異常;
- 因此:C++11采用RAII的方式對鎖進行了封裝,即lock_guard和unique_lock。
?
1、mutex的種類
在C++中,提供了幾種不同類型的互斥鎖,每種互斥鎖都適用于不同的使用場景和需求。
下面是一些常見的互斥鎖類型:
-
std::mutex:
- std::mutex是C++標準庫中最基本的互斥鎖類型,該類的對象之間不能拷貝,也不能進行移動
- 它提供了最基本的上鎖(lock)和解鎖(unlock)操作,可以保護臨界區(qū)的互斥訪問。
- std::mutex是非遞歸的,同一個線程多次對同一個互斥鎖上鎖會導致死鎖。
-
std::recursive_mutex:
- std::recursive_mutex是一個可遞歸的互斥鎖。
- 不同于std::mutex,std::recursive_mutex允許同一個線程多次對同一個互斥鎖上鎖。
- 使用遞歸鎖可以防止同一線程在同一個臨界區(qū)中出現(xiàn)死鎖的情況。
-
std::timed_mutex:
- std::timed_mutex是一個帶有超時機制的互斥鎖。
- 它提供了額外的功能,允許線程在嘗試獲得鎖時等待一定的時間,超過時間后可以執(zhí)行其他操作。
- std::timed_mutex可以通過成員函數(shù)try_lock_for()和try_lock_until()來實現(xiàn)嘗試獲得鎖的定時操作。
try_lock_for()
- 接受一個時間范圍,表示在這一段時間范圍之內(nèi)線程如果沒有獲得鎖則被阻塞住(與std::mutex 的 try_lock() 不同,try_lock 如果被調(diào)用時沒有獲得鎖則直接返回false),如果在此期間其他線程釋放了鎖,則該線程可以獲得對互斥量的鎖,如果超時(即在指定時間內(nèi)還是沒有獲得鎖),則返回 false。
try_lock_until()
- 接受一個時間點作為參數(shù),在指定時間點未到來之前線程如果沒有獲得鎖則被阻塞住,如果在此期間其他線程釋放了鎖,則該線程可以獲得對互斥量的鎖,如果超時(即在指定時間內(nèi)還是沒有獲得鎖),則返回 false。
-
std::recursive_timed_mutex:
- std::recursive_timed_mutex是一個同時具有遞歸和超時功能的互斥鎖。
- 它結(jié)合了std::recursive_mutex和std::timed_mutex的特性,可以遞歸地使用,并且支持超時等待。
?
2、lock_guard
lock_guard 是C++標準庫中定義的一個模板類,用于簡化互斥鎖的使用和管理。它提供了一種基于作用域的方式來自動管理互斥鎖的上鎖和解鎖操作,從而保證線程安全和避免死鎖等問題。
-
特性:
- lock_guard 使用了RAII技術(shù)。它會在構(gòu)造函數(shù)中對互斥鎖進行上鎖,而在析構(gòu)函數(shù)中自動對互斥鎖進行解鎖,無需手動處理。
- lock_guard 是非拷貝構(gòu)造和非移動構(gòu)造的,確保同一互斥鎖不會被多個 lock_guard 對象同時管理,避免了死鎖的可能性。
- lock_guard 對象在創(chuàng)建時自動進行上鎖,當其所在的作用域結(jié)束時,會自動調(diào)用析構(gòu)函數(shù)進行解鎖,無需手動編寫解鎖操作,從而簡化了編程。
-
使用方法:
- 首先,需要包含頭文件
<mutex>
來使用 lock_guard。 - 創(chuàng)建一個lock_guard 對象時,需要傳入一個互斥鎖對象作為參數(shù)。
- lock_guard 對象的作用域就是所謂的互斥區(qū)域,即需要保護的臨界區(qū)。
- 在 lock_guard 對象的作用域內(nèi),可以直接訪問共享資源,而無需手動上鎖和解鎖。
- 當 lock_guard 對象的作用域結(jié)束時,會自動調(diào)用析構(gòu)函數(shù),對互斥鎖進行解鎖。
- 首先,需要包含頭文件
下面是一個使用lock_guard的示例代碼:
mutex mtx; // 互斥鎖對象void Func()
{lock_guard<mutex> lock(mtx); // 創(chuàng)建std::lock_guard對象,關(guān)聯(lián)互斥鎖// 在臨界區(qū)內(nèi)訪問共享資源cout << "Thread ID: " << this_thread::get_id() << endl;
}int main()
{thread t1(Func);thread t2(Func);t1.join();t2.join();return 0;
}
輸出展示:
?【解釋說明】
- 當
t1
和t2
兩個線程執(zhí)行到lock_guard對象的作用域時,會自動上鎖互斥鎖mtx;
- 在作用域內(nèi)部,線程可以安全地訪問共享資源。當線程執(zhí)行完臨界區(qū)的代碼后,lock_guard 對象的析構(gòu)函數(shù)會自動調(diào)用,從而自動解鎖互斥鎖。
?
3、unique_lock
lock_guard的缺陷:太單一,用戶沒有辦法對該鎖進行控制,因此C++11又提供了unique_lock
特性:
- 與lock_gard類似,unique_lock類模板也是采用RAII的方式對鎖進行了封裝。
- 并且也是以獨占所有權(quán)的方式管理mutex對象的上鎖和解鎖操作,即其對象之間不能發(fā)生拷貝。在構(gòu)造(或移動(move)賦值)時,unique_lock 對象需要傳遞一個 Mutex 對象作為它的參數(shù),新創(chuàng)建的unique_lock 對象負責傳入的 Mutex 對象的上鎖和解鎖操作。
- 使用以上類型互斥量實例化unique_lock的對象時,自動調(diào)用構(gòu)造函數(shù)上鎖,unique_lock對象銷毀時自動調(diào)用析構(gòu)函數(shù)解鎖,可以很方便的防止死鎖問題。
與lock_guard不同的是,unique_lock更加的靈活,提供了更多的成員函數(shù):
- 上鎖/解鎖操作:lock、try_lock、try_lock_for、try_lock_until和unlock
- 修改操作:移動賦值、交換(swap:與另一個unique_lock對象互換所管理的互斥量所有權(quán))、釋放(release:返回它所管理的互斥量對象的指針,并釋放所有權(quán))
- 獲取屬性:owns_lock(返回當前對象是否上了鎖)、operator bool()(與owns_lock()的功能相
- 同)、mutex(返回當前unique_lock所管理的互斥量的指針)。
?
?下面是一個使用unique_lock的示例代碼:
?
總之,unique_lock?是一個更靈活和強大的互斥鎖管理類模板,它提供了比lock_guard更多的功能和選項,適用于復雜的線程同步需求,簡化了互斥鎖的使用和管理。
(五)condition_variable
condition_variable?是 C++ 標準庫中的一個同步原語,用于線程間的條件變量通信。它是一種等待-通知機制,允許一個或多個線程等待某個共享數(shù)據(jù)的狀態(tài)發(fā)生變化,并在滿足特定條件時被喚醒。
condition_variable 的主要成員函數(shù)包括:
wait(lock)
: 等待條件變量的通知,同時釋放互斥鎖,并進入等待狀態(tài)。當收到通知后,重新獲取互斥鎖,并繼續(xù)執(zhí)行。wait(lock, pred)
: 在滿足特定條件(由謂詞?pred
?指定)時等待條件變量的通知。notify_one()
: 喚醒等待隊列中的一個線程。notify_all()
: 喚醒等待隊列中的所有線程。
使用??condition_variable 需要配合一個 mutex 對象一起使用。通常的做法是,在對共享數(shù)據(jù)進行訪問之前,先獲得互斥鎖,并在條件不滿足的情況下調(diào)用 wait
等待條件變量的通知。
接下來我們通過condition_variable 結(jié)合上述學到的知識,設計支持出:支持兩個線程交替打印,一個打印奇數(shù),一個打印偶數(shù) 功能的代碼:
void two_thread_print()
{mutex mtx;condition_variable c;int n = 100;bool flag = true;thread t1([&]() {int i = 0;while (i < n){unique_lock<mutex> lock(mtx);c.wait(lock, [&]()->bool {return flag; });cout << i << endl;flag = false;i += 2; // 偶數(shù)c.notify_one();}});thread t2([&]() {int j = 1;while (j < n){unique_lock<mutex> lock(mtx);c.wait(lock, [&]()->bool {return !flag; });cout << j << endl;j += 2; // 奇數(shù)flag = true;c.notify_one();}});t1.join();t2.join();
}int main()
{two_thread_print();return 0;
}
總結(jié)
以上便是關(guān)于 C++11 線程庫的全部知識介紹。感謝大家的觀看與支持!!!
?
?