日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

C++多线程详细讲解

發(fā)布時(shí)間:2023/12/20 c/c++ 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++多线程详细讲解 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本文是純轉(zhuǎn)載,覺得大佬寫的非常好!如有侵權(quán)可以刪除
鏈接: link.

C++多線程基礎(chǔ)教程
目錄
1 什么是C++多線程?
2 C++多線程基礎(chǔ)知識(shí)
2.1 創(chuàng)建線程
2.2 互斥量使用
lock()與unlock():
lock_guard():
unique_lock:
condition_variable:
2.3 異步線程
async與future:
shared_future
2.4 原子類型automic
實(shí)例
生產(chǎn)者消費(fèi)者問題
4 C++多線程高級(jí)知識(shí)
4.1 線程池
線程池基礎(chǔ)知識(shí)
線程池的實(shí)現(xiàn)
5 延伸拓展
最后一次更新日期:2020.08.23

1 什么是C++多線程?

線程:線程是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位,它被包含在進(jìn)程之中,進(jìn)程包含一個(gè)或者多個(gè)線程。進(jìn)程可以理解為完成一件事的完整解決方案,而線程可以理解為這個(gè)解決方案中的的一個(gè)步驟,可能這個(gè)解決方案就這只有一個(gè)步驟,也可能這個(gè)解決方案有多個(gè)步驟。
多線程:多線程是實(shí)現(xiàn)并發(fā)(并行)的手段,并發(fā)(并行)即多個(gè)線程同時(shí)執(zhí)行,一般而言,多線程就是把執(zhí)行一件事情的完整步驟拆分為多個(gè)子步驟,然后使得這多個(gè)步驟同時(shí)執(zhí)行。
C++多線程:(簡單情況下)C++多線程使用多個(gè)函數(shù)實(shí)現(xiàn)各自功能,然后將不同函數(shù)生成不同線程,并同時(shí)執(zhí)行這些線程(不同線程可能存在一定程度的執(zhí)行先后順序,但總體上可以看做同時(shí)執(zhí)行)。
上述概念很容易因表述不準(zhǔn)確而造成誤解,這里沒有深究線程與進(jìn)程,并發(fā)與并行的概念,以上僅為一種便于理解的表述,如果有任何問題還請(qǐng)指正,若有更好的表述,也歡迎留言分享。

2 C++多線程基礎(chǔ)知識(shí)

2.1 創(chuàng)建線程

首先要引入頭文件#include(C++11的標(biāo)準(zhǔn)庫中提供了多線程庫),該頭文件中定義了thread類,創(chuàng)建一個(gè)線程即實(shí)例化一個(gè)該類的對(duì)象,實(shí)例化對(duì)象時(shí)候調(diào)用的構(gòu)造函數(shù)需要傳遞一個(gè)參數(shù),該參數(shù)就是函數(shù)名,thread th1(proc1);如果傳遞進(jìn)去的函數(shù)本身需要傳遞參數(shù),實(shí)例化對(duì)象時(shí)將這些參數(shù)按序?qū)懙胶瘮?shù)名后面,thread th1(proc1,a,b);只要?jiǎng)?chuàng)建了線程對(duì)象(傳遞“函數(shù)名/可調(diào)用對(duì)象”作為參數(shù)的情況下),線程就開始執(zhí)行(std::thread 有一個(gè)無參構(gòu)造函數(shù)重載的版本,不會(huì)創(chuàng)建底層的線程)。
有兩種線程阻塞方法join()與detach(),阻塞線程的目的是調(diào)節(jié)各線程的先后執(zhí)行順序,這里重點(diǎn)講join()方法,不推薦使用detach(),detach()使用不當(dāng)會(huì)發(fā)生引用對(duì)象失效的錯(cuò)誤。當(dāng)線程啟動(dòng)后,一定要在和線程相關(guān)聯(lián)的thread對(duì)象銷毀前,對(duì)線程運(yùn)用join()或者detach()。
join(), 當(dāng)前線程暫停, 等待指定的線程執(zhí)行結(jié)束后, 當(dāng)前線程再繼續(xù)。th1.join(),即該語句所在的線程(該語句寫在main()函數(shù)里面,即主線程內(nèi)部)暫停,等待指定線程(指定線程為th1)執(zhí)行結(jié)束后,主線程再繼續(xù)執(zhí)行。
整個(gè)過程就相當(dāng)于你在做某件事情,中途你讓老王幫你辦一個(gè)任務(wù)(你辦的時(shí)候他同時(shí)辦)(創(chuàng)建線程1),又叫老李幫你辦一件任務(wù)(創(chuàng)建線程2),現(xiàn)在你的這部分工作做完了,需要用到他們的結(jié)果,只需要等待老王和老李處理完(join(),阻塞主線程),等他們把任務(wù)做完(子線程運(yùn)行結(jié)束),你又可以開始你手頭的工作了(主線程不再阻塞)。

#include<iostream> #include<thread> using namespace std; void proc(int a) {cout << "我是子線程,傳入?yún)?shù)為" << a << endl;cout << "子線程中顯示子線程id為" << this_thread::get_id()<< endl; } int main() {cout << "我是主線程" << endl;int a = 9;thread th2(proc,a);//第一個(gè)參數(shù)為函數(shù)名,第二個(gè)參數(shù)為該函數(shù)的第一個(gè)參數(shù),如果該函數(shù)接收多個(gè)參數(shù)就依次寫在后面。此時(shí)線程開始執(zhí)行。cout << "主線程中顯示子線程id為" << th2.get_id() << endl;th2.join()//此時(shí)主線程被阻塞直至子線程執(zhí)行結(jié)束。return 0; }

2.2 互斥量使用

什么是互斥量?

這樣比喻,單位上有一臺(tái)打印機(jī)(共享數(shù)據(jù)a),你要用打印機(jī)(線程1要操作數(shù)據(jù)a),同事老王也要用打印機(jī)(線程2也要操作數(shù)據(jù)a),但是打印機(jī)同一時(shí)間只能給一個(gè)人用,此時(shí),規(guī)定不管是誰,在用打印機(jī)之前都要向領(lǐng)導(dǎo)申請(qǐng)?jiān)S可證(lock),用完后再向領(lǐng)導(dǎo)歸還許可證(unlock),許可證總共只有一個(gè),沒有許可證的人就等著在用打印機(jī)的同事用完后才能申請(qǐng)?jiān)S可證(阻塞,線程1lock互斥量后其他線程就無法lock,只能等線程1unlock后,其他線程才能lock),那么,這個(gè)許可證就是互斥量。互斥量保證了使用打印機(jī)這一過程不被打斷。

程序?qū)嵗痬utex對(duì)象m,線程調(diào)用成員函數(shù)m.lock()會(huì)發(fā)生下面 3 種情況:
(1)如果該互斥量當(dāng)前未上鎖,則調(diào)用線程將該互斥量鎖住,直到調(diào)用unlock()之前,該線程一直擁有該鎖。
(2)如果該互斥量當(dāng)前被鎖住,則調(diào)用線程被阻塞,直至該互斥量被解鎖。

互斥量怎么使用?

首先需要#include

lock()與unlock():

#include<iostream> #include<thread> #include<mutex> using namespace std; mutex m;//實(shí)例化m對(duì)象,不要理解為定義變量 void proc1(int a) {m.lock();cout << "proc1函數(shù)正在改寫a" << endl;cout << "原始a為" << a << endl;cout << "現(xiàn)在a為" << a + 2 << endl;m.unlock(); }void proc2(int a) {m.lock();cout << "proc2函數(shù)正在改寫a" << endl;cout << "原始a為" << a << endl;cout << "現(xiàn)在a為" << a + 1 << endl;m.unlock(); } int main() {int a = 0;thread proc1(proc1, a);thread proc2(proc2, a);proc1.join();proc2.join();return 0; }

不推薦實(shí)直接去調(diào)用成員函數(shù)lock(),因?yàn)槿绻泆nlock(),將導(dǎo)致鎖無法釋放,使用lock_guard或者unique_lock能避免忘記解鎖這種問題。

lock_guard():
其原理是:聲明一個(gè)局部的lock_guard對(duì)象,在其構(gòu)造函數(shù)中進(jìn)行加鎖,在其析構(gòu)函數(shù)中進(jìn)行解鎖。最終的結(jié)果就是:創(chuàng)建即加鎖,作用域結(jié)束自動(dòng)解鎖。從而使用lock_guard()就可以替代lock()與unlock()。
通過設(shè)定作用域,使得lock_guard在合適的地方被析構(gòu)(在互斥量鎖定到互斥量解鎖之間的代碼叫做臨界區(qū)(需要互斥訪問共享資源的那段代碼稱為臨界區(qū)),臨界區(qū)范圍應(yīng)該盡可能的小,即lock互斥量后應(yīng)該盡早unlock),通過使用{}來調(diào)整作用域范圍,可使得互斥量m在合適的地方被解鎖:

#include<iostream> #include<thread> #include<mutex> using namespace std; mutex m;//實(shí)例化m對(duì)象,不要理解為定義變量 void proc1(int a) {lock_guard<mutex> g1(m);//用此語句替換了m.lock();lock_guard傳入一個(gè)參數(shù)時(shí),該參數(shù)為互斥量,此時(shí)調(diào)用了lock_guard的構(gòu)造函數(shù),申請(qǐng)鎖定mcout << "proc1函數(shù)正在改寫a" << endl;cout << "原始a為" << a << endl;cout << "現(xiàn)在a為" << a + 2 << endl; }//此時(shí)不需要寫m.unlock(),g1出了作用域被釋放,自動(dòng)調(diào)用析構(gòu)函數(shù),于是m被解鎖void proc2(int a) {{lock_guard<mutex> g2(m);cout << "proc2函數(shù)正在改寫a" << endl;cout << "原始a為" << a << endl;cout << "現(xiàn)在a為" << a + 1 << endl;}//通過使用{}來調(diào)整作用域范圍,可使得m在合適的地方被解鎖cout << "作用域外的內(nèi)容3" << endl;cout << "作用域外的內(nèi)容4" << endl;cout << "作用域外的內(nèi)容5" << endl; } int main() {int a = 0;thread proc1(proc1, a);thread proc2(proc2, a);proc1.join();proc2.join();return 0; }

lock_gurad也可以傳入兩個(gè)參數(shù),第一個(gè)參數(shù)為adopt_lock標(biāo)識(shí)時(shí),表示不再構(gòu)造函數(shù)中不再進(jìn)行互斥量鎖定,因此此時(shí)需要提前手動(dòng)鎖定。

#include<iostream> #include<thread> #include<mutex> using namespace std; mutex m;//實(shí)例化m對(duì)象,不要理解為定義變量 void proc1(int a) {m.lock();//手動(dòng)鎖定lock_guard<mutex> g1(m,adopt_lock);cout << "proc1函數(shù)正在改寫a" << endl;cout << "原始a為" << a << endl;cout << "現(xiàn)在a為" << a + 2 << endl; }//自動(dòng)解鎖void proc2(int a) {lock_guard<mutex> g2(m);//自動(dòng)鎖定cout << "proc2函數(shù)正在改寫a" << endl;cout << "原始a為" << a << endl;cout << "現(xiàn)在a為" << a + 1 << endl; }//自動(dòng)解鎖 int main() {int a = 0;thread proc1(proc1, a);thread proc2(proc2, a);proc1.join();proc2.join();return 0; }

unique_lock:
unique_lock類似于lock_guard,只是unique_lock用法更加豐富,同時(shí)支持lock_guard()的原有功能。
使用lock_guard后不能手動(dòng)lock()與手動(dòng)unlock();使用unique_lock后可以手動(dòng)lock()與手動(dòng)unlock();
unique_lock的第二個(gè)參數(shù),除了可以是adopt_lock,還可以是try_to_lock與defer_lock;
try_to_lock: 嘗試去鎖定,得保證鎖處于unlock的狀態(tài),然后嘗試現(xiàn)在能不能獲得鎖;嘗試用mutx的lock()去鎖定這個(gè)mutex,但如果沒有鎖定成功,會(huì)立即返回,不會(huì)阻塞在那里
defer_lock: 始化了一個(gè)沒有加鎖的mutex;

lock_guard unique_lock
手動(dòng)lock與手動(dòng)unlock 不支持 支持
參數(shù) 支持adopt_lock 支持adopt_lock/try_to_lock/defer_lock

#include<iostream> #include<thread> #include<mutex> using namespace std; mutex m; void proc1(int a) {unique_lock<mutex> g1(m, defer_lock);//始化了一個(gè)沒有加鎖的mutexcout << "不拉不拉不拉" << endl;g1.lock();//手動(dòng)加鎖,注意,不是m.lock();注意,不是m.lock();注意,不是m.lock()cout << "proc1函數(shù)正在改寫a" << endl;cout << "原始a為" << a << endl;cout << "現(xiàn)在a為" << a + 2 << endl;g1.unlock();//臨時(shí)解鎖cout << "不拉不拉不拉" << endl;g1.lock();cout << "不拉不拉不拉" << endl; }//自動(dòng)解鎖void proc2(int a) {unique_lock<mutex> g2(m,try_to_lock);//嘗試加鎖,但如果沒有鎖定成功,會(huì)立即返回,不會(huì)阻塞在那里;cout << "proc2函數(shù)正在改寫a" << endl;cout << "原始a為" << a << endl;cout << "現(xiàn)在a為" << a + 1 << endl; }//自動(dòng)解鎖 int main() {int a = 0;thread proc1(proc1, a);thread proc2(proc2, a);proc1.join();proc2.join();return 0; } unique_lock所有權(quán)的轉(zhuǎn)移mutex m; { unique_lock<mutex> g2(m,defer_lock);unique_lock<mutex> g3(move(g2));//所有權(quán)轉(zhuǎn)移,此時(shí)由g3來管理互斥量mg3.lock();g3.unlock();g3.lock(); }

condition_variable:
需要#include<condition_variable>;
wait(locker):在線程被阻塞時(shí),該函數(shù)會(huì)自動(dòng)調(diào)用 locker.unlock() 釋放鎖,使得其他被阻塞在鎖競爭上的線程得以繼續(xù)執(zhí)行。另外,一旦當(dāng)前線程獲得通知(通常是另外某個(gè)線程調(diào)用 notify_* 喚醒了當(dāng)前線程),wait() 函數(shù)此時(shí)再自動(dòng)調(diào)用 locker.lock()。
notify_all():隨機(jī)喚醒一個(gè)等待的線程
notify_once():喚醒所有等待的線程

2.3 異步線程

需要#include

async與future:
async是一個(gè)函數(shù)模板,用來啟動(dòng)一個(gè)異步任務(wù),它返回一個(gè)future類模板對(duì)象,future對(duì)象起到了占位的作用,剛實(shí)例化的future是沒有儲(chǔ)存值的,但在調(diào)用future對(duì)象的get()成員函數(shù)時(shí),主線程會(huì)被阻塞直到異步線程執(zhí)行結(jié)束,并把返回結(jié)果傳遞給future,即通過FutureObject.get()獲取函數(shù)返回值。

相當(dāng)于你去辦政府辦業(yè)務(wù)(主線程),把資料交給了前臺(tái),前臺(tái)安排了人員去給你辦理(async創(chuàng)建子線程),前臺(tái)給了你一個(gè)單據(jù)(future對(duì)象),說你的業(yè)務(wù)正在給你辦(子線程正在運(yùn)行),等段時(shí)間你再過來憑這個(gè)單據(jù)取結(jié)果。過了段時(shí)間,你去前臺(tái)取結(jié)果,但是結(jié)果還沒出來(子線程還沒return),你就在前臺(tái)等著(阻塞),直到你拿到結(jié)果(get())你才離開(不再阻塞)。

#include <iostream> #include <thread> #include <mutex> #include<future> #include<Windows.h> using namespace std; double t1(const double a, const double b) {double c = a + b;Sleep(3000);//假設(shè)t1函數(shù)是個(gè)復(fù)雜的計(jì)算過程,需要消耗3秒return c; }int main() {double a = 2.3;double b = 6.7;future<double> fu = async(t1, a, b);//創(chuàng)建異步線程線程,并將線程的執(zhí)行結(jié)果用fu占位;cout << "正在進(jìn)行計(jì)算" << endl;cout << "計(jì)算結(jié)果馬上就準(zhǔn)備好,請(qǐng)您耐心等待" << endl;cout << "計(jì)算結(jié)果:" << fu.get() << endl;//阻塞主線程,直至異步線程return//cout << "計(jì)算結(jié)果:" << fu.get() << endl;//取消該語句注釋后運(yùn)行會(huì)報(bào)錯(cuò),因?yàn)閒uture對(duì)象的get()方法只能調(diào)用一次。return 0; }

shared_future
future與shard_future的用途都是為了占位,但是兩者有些許差別。
future的get()成員函數(shù)是轉(zhuǎn)移數(shù)據(jù)所有權(quán);shared_future的get()成員函數(shù)是復(fù)制數(shù)據(jù)。
因此:
future對(duì)象的get()只能調(diào)用一次;無法實(shí)現(xiàn)多個(gè)線程等待同一個(gè)異步線程,一旦其中一個(gè)線程獲取了異步線程的返回值,其他線程就無法再次獲取。
shared_future對(duì)象的get()可以調(diào)用多次;可以實(shí)現(xiàn)多個(gè)線程等待同一個(gè)異步線程,每個(gè)線程都可以獲取異步線程的返回值。

future shared_future
語義 轉(zhuǎn)移 賦值
可否多次調(diào)用 否 可

2.4 原子類型automic

原子操作指“不可分割的操作”;也就是說這種操作狀態(tài)要么是完成的,要么是沒完成的。互斥量的加鎖一般是針對(duì)一個(gè)代碼段,而原子操作針對(duì)的一般都是一個(gè)變量。
automic是一個(gè)模板類,使用該模板類實(shí)例化的對(duì)象,提供了一些保證原子性的成員函數(shù)來實(shí)現(xiàn)共享數(shù)據(jù)的常用操作。

可以這樣理解:
在以前,定義了一個(gè)共享的變量(int i=0),多個(gè)線程會(huì)操作這個(gè)變量,那么每次操作這個(gè)變量時(shí),都是用lock加鎖,操作完畢使用unlock解鎖,以保證線程之間不會(huì)沖突;
現(xiàn)在,實(shí)例化了一個(gè)類對(duì)象(automic I=0)來代替以前的那個(gè)變量,每次操作這個(gè)對(duì)象時(shí),就不用lock與unlock,這個(gè)對(duì)象自身就具有原子性,以保證線程之間不會(huì)沖突。

automic對(duì)象提供了常見的原子操作(通過調(diào)用成員函數(shù)實(shí)現(xiàn)對(duì)數(shù)據(jù)的原子操作):
store是原子寫操作,load是原子讀操作。exchange是于兩個(gè)數(shù)值進(jìn)行交換的原子操作。
即使使用了automic,也要注意執(zhí)行的操作是否支持原子性。一般atomic原子操作,針對(duì)++,–,+=,-=,&=,|=,^=是支持的。

實(shí)例
前一章內(nèi)容為了簡單的說明一些函數(shù)的用法,所列舉的例子有些牽強(qiáng),因此在本章列舉了一些多線程常見的實(shí)例

生產(chǎn)者消費(fèi)者問題
/*

生產(chǎn)者消費(fèi)者問題 */ #include <iostream> #include <deque> #include <thread> #include <mutex> #include <condition_variable> #include<Windows.h> using namespace std;deque<int> q; mutex mu; condition_variable cond; int c = 0;//緩沖區(qū)的產(chǎn)品個(gè)數(shù)void producer() { int data1;while (1) {//通過外層循環(huán),能保證生成用不停止if(c < 3) {//限流{data1 = rand();unique_lock<mutex> locker(mu);//鎖q.push_front(data1);cout << "存了" << data1 << endl;cond.notify_one(); // 通知取++c;}Sleep(500);}} }void consumer() {int data2;//data用來覆蓋存放取的數(shù)據(jù)while (1) {{unique_lock<mutex> locker(mu);while(q.empty())cond.wait(locker); //wati()阻塞前先會(huì)解鎖,解鎖后生產(chǎn)者才能獲得鎖來放產(chǎn)品到緩沖區(qū);生產(chǎn)者notify后,將不再阻塞,且自動(dòng)又獲得了鎖。data2 = q.back();//取的第一步q.pop_back();//取的第二步cout << "取了" << data2<<endl;--c;}Sleep(1500);} } int main() {thread t1(producer);thread t2(consumer);t1.join();t2.join();return 0; }

4 C++多線程高級(jí)知識(shí)

4.1 線程池

線程池基礎(chǔ)知識(shí)
不采用線程池時(shí):

創(chuàng)建線程 -> 由該線程執(zhí)行任務(wù) -> 任務(wù)執(zhí)行完畢后銷毀線程。即使需要使用到大量線程,每個(gè)線程都要按照這個(gè)流程來創(chuàng)建、執(zhí)行與銷毀。

雖然創(chuàng)建與銷毀線程消耗的時(shí)間 遠(yuǎn)小于 線程執(zhí)行的時(shí)間,但是對(duì)于需要頻繁創(chuàng)建大量線程的任務(wù),創(chuàng)建與銷毀線程 所占用的時(shí)間與CPU資源也會(huì)有很大占比。

為了減少創(chuàng)建與銷毀線程所帶來的時(shí)間消耗與資源消耗,因此采用線程池的策略:

程序啟動(dòng)后,預(yù)先創(chuàng)建一定數(shù)量的線程放入空閑隊(duì)列中,這些線程都是處于阻塞狀態(tài),基本不消耗CPU,只占用較小的內(nèi)存空間。

接收到任務(wù)后,線程池選擇一個(gè)空閑線程來執(zhí)行此任務(wù)。

任務(wù)執(zhí)行完畢后,不銷毀線程,線程繼續(xù)保持在池中等待下一次的任務(wù)。

線程池所解決的問題:

(1) 需要頻繁創(chuàng)建與銷毀大量線程的情況下,減少了創(chuàng)建與銷毀線程帶來的時(shí)間開銷和CPU資源占用。(省時(shí)省力)

(2) 實(shí)時(shí)性要求較高的情況下,由于大量線程預(yù)先就創(chuàng)建好了,接到任務(wù)就能馬上從線程池中調(diào)用線程來處理任務(wù),略過了創(chuàng)建線程這一步驟,提高了實(shí)時(shí)性。(實(shí)時(shí))

線程池的實(shí)現(xiàn)
待更新。

延伸拓展

創(chuàng)建類,除了傳遞函數(shù)外,還可以使用:Lambda表達(dá)式、可調(diào)用類的實(shí)例。
線程與進(jìn)程:
并發(fā)與并行:
并發(fā)與并行并不是非此即彼的概念
并發(fā):同一時(shí)間發(fā)生兩件及以上的事情。
線程并不是越多越好,每個(gè)線程都需要一個(gè)獨(dú)立的堆??臻g,線程切換也會(huì)耗費(fèi)時(shí)間。
并行:

detach():

未完待續(xù)

總結(jié)

以上是生活随笔為你收集整理的C++多线程详细讲解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。