C++面向?qū)ο蟪绦蛟O(shè)計(jì)(第4版)-課件 CH9_第1頁
C++面向?qū)ο蟪绦蛟O(shè)計(jì)(第4版)-課件 CH9_第2頁
C++面向?qū)ο蟪绦蛟O(shè)計(jì)(第4版)-課件 CH9_第3頁
C++面向?qū)ο蟪绦蛟O(shè)計(jì)(第4版)-課件 CH9_第4頁
C++面向?qū)ο蟪绦蛟O(shè)計(jì)(第4版)-課件 CH9_第5頁
已閱讀5頁,還剩38頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

第9章線程本章主要教學(xué)內(nèi)容程序、進(jìn)程和線程三者的基本概念和關(guān)系簡單的Windows和unix

Api線程程序設(shè)計(jì)簡單標(biāo)準(zhǔn)C++線程程序設(shè)計(jì)類與線程簡單的線程同步程序程序設(shè)計(jì)本章教學(xué)重點(diǎn)理解線程、進(jìn)程和程序的關(guān)系,掌握線程程序的基本結(jié)構(gòu)和運(yùn)行原理WindowsAPI線程的結(jié)構(gòu)和程序設(shè)計(jì)標(biāo)準(zhǔn)C++線程函數(shù)的結(jié)構(gòu)、調(diào)用方法和簡單線程設(shè)計(jì)線程同步及程序設(shè)計(jì):互斥鎖、讀寫鎖、信號量、條件變量的簡單應(yīng)用程序設(shè)計(jì)類與線程:類成員函數(shù)線程設(shè)計(jì)。教學(xué)難點(diǎn)線程運(yùn)行原理、主線程與子線程運(yùn)行關(guān)系控制方法線程同步,互斥鎖、信號量、條件變量控制的線程同步程序設(shè)計(jì)9.1程序、進(jìn)程和線程1、程序、進(jìn)程和線程的概念程序(program)是用程序設(shè)計(jì)語言編寫的用來完成特定任務(wù)的一組命令集合,以文件形式保存在各種存儲設(shè)備中,是靜態(tài)的。進(jìn)程(process)是被裝載到內(nèi)存中處于運(yùn)行狀態(tài)的程序,是動態(tài)的,要經(jīng)歷從外存載入內(nèi)存,在內(nèi)存中運(yùn)行,運(yùn)行完成后從內(nèi)存中清除的完整過程,這個過程稱為進(jìn)程的生命周期。線程(thread)是進(jìn)程中執(zhí)行運(yùn)算的最小單位,是從一個進(jìn)程中劃分出來的更小指令集合,該指令集合能夠被CPU作為一個獨(dú)立單元進(jìn)行調(diào)度和執(zhí)行。多線程,如果一個進(jìn)程內(nèi)可以劃分出多個線程,并允許在同一時間并行執(zhí)行它們,就稱為多線程。在多線程系統(tǒng)中,單個線程并不擁有系統(tǒng)資源,而是只擁有少量在運(yùn)行過程中必不可少的資源(程序計(jì)數(shù)器,一組寄存器和棧),系統(tǒng)資源由同一個進(jìn)程中的各線程共享,因此線程之間的通信簡便,調(diào)度切換效率高。9.1程序、進(jìn)程和線程2、程序、進(jìn)程和線程的關(guān)系一個程序可以對應(yīng)多個進(jìn)程,一個進(jìn)程中可以包括多個線程;反之,一個線程只對應(yīng)一個進(jìn)程,一個進(jìn)程只能對應(yīng)一個程序。9.1程序、進(jìn)程和線程3、線程的發(fā)展和優(yōu)勢在早期計(jì)算機(jī)系統(tǒng)中,操作系統(tǒng)進(jìn)行資源分配和獨(dú)立調(diào)度執(zhí)行的基本單位是進(jìn)程。在單核CPU時代,這種程序執(zhí)行方式并無大礙。對稱處理機(jī)(SymmetricMulti-Processing,SMP,在一臺計(jì)算機(jī)中匯集了一組處理器)和多核心CPU的出現(xiàn),以進(jìn)程為調(diào)度執(zhí)行單位出現(xiàn)了許多弊端。一是進(jìn)程作為資源擁有者,在創(chuàng)建、撤銷與切換時存在較大的時空開銷;二是由于多處理機(jī)可以同時滿足多個運(yùn)行單位,而多進(jìn)程并行執(zhí)行的開銷過大,進(jìn)程間切換的效率較低,于是在20世紀(jì)80年代出現(xiàn)了線程。線程優(yōu)勢:線程比進(jìn)程更小,能夠共享同一進(jìn)程內(nèi)的資源,線程間的調(diào)度切換代價小,速度快,可以在多核CPU中并發(fā)運(yùn)行,提高了系統(tǒng)資源的利用率和吞吐量在四核心CPU系統(tǒng)中,每個CPU中以獨(dú)立執(zhí)行進(jìn)程中的1個線程,4個線程。若以程為執(zhí)行單位,則有CPU處于空閑狀態(tài)。顯然線程效率更高!9.1程序、進(jìn)程和線程3、C++11標(biāo)準(zhǔn)之前的線程程序設(shè)計(jì)11標(biāo)準(zhǔn)之前的C++不支持線程,如果要在C++中進(jìn)行線程設(shè)計(jì),需要通過操作系統(tǒng)提供的線程API(ApplicationProgrammingInterface)函數(shù)才能夠?qū)崿F(xiàn)。主流操作系統(tǒng)又分為Linux和Windows兩大系列,兩者線程API并不相同。Linux線程設(shè)計(jì)在Linux的pthread.h頭文件中,提供了創(chuàng)建線程的API函數(shù):intpthread_create(pthread_t*thread, //返回創(chuàng)建的線程IDconstpthread_attr_t*attr,//設(shè)置線程屬性,通常為NULL(系統(tǒng)默認(rèn)屬性)

void*(start_routine)(void*),

//設(shè)置線程函數(shù)

void*arg); //傳入所設(shè)置的線程函數(shù)的參數(shù)9.1程序、進(jìn)程和線程3、C++11標(biāo)準(zhǔn)之前的線程程序設(shè)計(jì)Windows線程設(shè)計(jì)在windows.h頭文件中,提供了創(chuàng)建線程的API函數(shù):HANDLECreateThread(LPSECURITY_ATTRIBUTESlpThreadAttributes,//系統(tǒng)安全屬性,常取NULLSIZE_TdwStackSize,//線程棧大小,常設(shè)為0(表示用系統(tǒng)默認(rèn)值)

LPTHREAD_START_ROUTINELpstartAddress, //設(shè)置線程函數(shù)

LPVOIDlpParameter,//傳入設(shè)置的線程函數(shù)的參數(shù)

DWORDdwCreationFlags,//設(shè)置啟動方式,常設(shè)為0(創(chuàng)建后立即啟動)

LPDWORDlpThreadID//返回創(chuàng)建的線程ID);【例9-1】簡單WindowsAPI線程設(shè)計(jì)。#include<windows.h> //線程API函數(shù)定義在此頭文件中#include<stdio.h>DWORDCALLBACKWinThread(LPVOIDlpParamters){//L1,Windows線程

while(true){staticinti=0;Sleep(1000); //L2printf("%d秒過去了\n",++i);}return0;}intmain(){DWORDthreadID;HANDLEhthread=CreateThread(NULL,0,WinThread,NULL,0,&threadID); //L3if(hthread==NULL){ //L4printf("線程創(chuàng)建失敗.\n");return-1;}printf("thread:%d\n",threadID);//L5Sleep(5000);//L6return0;}運(yùn)行結(jié)果如下:thread:84561秒過去了2秒過去了3秒過去了4秒過去了5秒過去了9.1程序、進(jìn)程和線程4、C++11標(biāo)準(zhǔn)的線程

Windows和Linux的API線程只能在對應(yīng)的操作系統(tǒng)中運(yùn)行,不具跨平臺運(yùn)行能力,并有設(shè)計(jì)麻煩。為解決上面的問題,c++11提供了線程標(biāo)準(zhǔn),支持跨平臺的線程設(shè)計(jì)。9.1程序、進(jìn)程和線程5、C++線程設(shè)計(jì)方法可以在linux或windows平臺建立的C++開發(fā)環(huán)境中,用C++標(biāo)準(zhǔn)提供的線程類設(shè)計(jì)線程程序,其設(shè)計(jì)方法相同。因?yàn)?,C++11標(biāo)準(zhǔn)中的線程類thread具有跨平臺運(yùn)行能力,同一個線程程序可以在Linux和Windows操作系統(tǒng)中運(yùn)行。基本步驟:#include<thread>頭文件,要用其中的thread類創(chuàng)建線程對象定義線程函數(shù)(一個普通函數(shù)),如:funcname(typepara1,…,typeparan);調(diào)用線程類的構(gòu)造函數(shù)創(chuàng)建線程對象:threadt(funcname,para1,…,paran);其中,thread是線程類,funcname是第2步定義的線程函數(shù),para1、…、panrn是傳遞給線程函數(shù)funcname的調(diào)用實(shí)參。將線程與線程對象分離或加入線程阻塞對列(解決主線程main和子線程的生命期不同步問題,防止主線程生命期結(jié)束而子線程仍在運(yùn)行所帶來的問題。t.detach()t.join()9.1程序、進(jìn)程和線程5、C++線程設(shè)計(jì)方法可以在linux或windows平臺建立的C++開發(fā)環(huán)境中,用C++標(biāo)準(zhǔn)提供的線程類設(shè)計(jì)線程程序,其設(shè)計(jì)方法相同。因?yàn)?,C++11標(biāo)準(zhǔn)中的線程類thread具有跨平臺運(yùn)行能力,同一個線程程序可以在Linux和Windows操作系統(tǒng)中運(yùn)行?;静襟E:#include<thread>頭文件,要用其中的thread類創(chuàng)建線程對象定義線程函數(shù)(一個普通函數(shù)),如:funcname(typepara1,…,typeparan);調(diào)用線程類的構(gòu)造函數(shù)創(chuàng)建線程對象:threadt(funcname,para1,…,paran);其中,thread是線程類,funcname是第2步定義的線程函數(shù),para1、…、panrn是傳遞給線程函數(shù)funcname的調(diào)用實(shí)參。將線程與線程對象分離或加入線程阻塞對列(解決主線程main和子線程的生命期不同步問題,防止主線程生命期結(jié)束而子線程仍在運(yùn)行所帶來的問題。t.detach()t.join()【例9-2】

用C++11的線程類thread創(chuàng)建無參線程函數(shù)thread1(

)和有參線程函數(shù)thread2(

)。#include<iostream>#include<thread>#include<Windows.h>usingnamespacestd;voidthread1(){//L1,普通無參函數(shù)

while(true){Sleep(1000);cout<<"thread1"<<endl;}}voidthread2(inta,intb){//L2,普通有參函數(shù)

while(true){Sleep(500);cout<<"thread2:"<<a<<"+"<<b<<"="<<a+b<<endl;}}intmain(){

std::threadt1(thread1);//L3,創(chuàng)建無參線程

std::threadt2(thread2,1,2);//L4,創(chuàng)建有參線程

Sleep(2000);//等待2秒鐘//t1.detach();//L5,讓線程與對象t1分離

//t2.detach();//L6,讓線程與對象t2分離

return0;}程序運(yùn)行輸出結(jié)果:thread2:1+2=3thread1thread2:1+2=3thread2:1+2=3Endmain

//L7,主線程main結(jié)束thread1thread2:1+2=3thread2:1+2=3……

//L8,不斷重復(fù)上面的輸出terminatecalledwithoutanactiveexception程序運(yùn)行2秒后因異常而崩潰!其原因是主線程main(

)在休眠2秒鐘后會結(jié)束運(yùn)行,線程對象t1和t2會因程序結(jié)束而被銷毀,但綁定到線程對象t1和t2上的線程還會繼續(xù)運(yùn)行,因此產(chǎn)生錯誤。L7位置就是main線程結(jié)束時輸出的,其后的輸出表明main(

)結(jié)束后,子線程仍然在運(yùn)行狀態(tài)中解決辦法之一:取消L5,L6的注釋,讓線程對象t1,t2與其綁定的子線程分離。9.2線程等待和線程ID獲取線程對象與綁定線程生命期不等的解決辦法用detch分離線程對象與其綁定的線程用join將線程加入阻塞隊(duì)列,稱為線程等待detach()和join()是線程類thread提的兩個成員函數(shù),主要用于處理主線程與子線程之間的控制流程,解決主線程結(jié)束了而子線程仍然在運(yùn)行的問題。detach()的作用是將子線程與創(chuàng)建它的線程對象分離開來,以此避免線程對象與它擁有的線程具有不同生存期的矛盾,其用法如例9-2所示(語句L5和L6),但此方法可以產(chǎn)生主線程結(jié)束后,子線程仍在不停執(zhí)行的問題。例如,在例9.2中,取消語句L5,L6的注釋,子線程thread1,thread2與對應(yīng)的線程對象t1,t2分離后,隨著主線程main的結(jié)束,t1,t2對象也因失去作用域而被正常銷毀。但線程thread1,thread2因與對應(yīng)線程對象已經(jīng)脫離綁定關(guān)系而獨(dú)立運(yùn)行,由于線程中存在死循環(huán),所以會永遠(yuǎn)執(zhí)行下去。9.2.1線程等待1、為什么要使用線程等待C++的線程由線程對象創(chuàng)建和管理,線程對象受到作用域和生命期控制,而線程實(shí)際上一種是一種普通程序,程序邏輯可能出現(xiàn)線程對象的生命期結(jié)束了但其綁定線程仍然在運(yùn)行的情況。如前所述,讓線程與線程對象脫離是一種解決辦法,但可以出現(xiàn)不合理的線程執(zhí)行邏輯,如例9-2所法。另一種常有的解決辦法就是線程等待。線程等待可以簡單理解為:將子線程加入讓主線程等待的隊(duì)列,即讓子線程執(zhí)行完成后主線程再繼續(xù)執(zhí)行的線程隊(duì)列(一個阻塞主線程讓其中的線程先執(zhí)行的子線程隊(duì)列,也稱作線程阻塞)。在這種情況下,子線程可以安全地訪問主線程中的資源,主線程則會等待子線程結(jié)束,回收子線程占用的系統(tǒng)資源后,再繼續(xù)執(zhí)行。2、線程等待(線程阻塞)、分離的編程方法用thread線程類的join()、detach()阻塞主線程,分離線程對象與其綁定線程。語法:threadt(treadfunc,para1…);//創(chuàng)建線程對象tt.joinable(); //判斷t是否可以加入join隊(duì)列,返回true或falset.join();//阻塞主線程,讓主線程等待t優(yōu)先執(zhí)行t.detach();//分離t和它控制的線程,讓線程獨(dú)立說明:僅當(dāng)子線程處于運(yùn)行狀態(tài)(可用joinable()判斷),一個線程對象才能執(zhí)行join或detach操作中的一個(兩者不能同時執(zhí)行),而且只允許調(diào)用一次?!纠?-3】修改例9-2,讓主線程等待子程結(jié)束后再退出。//Eg9-3.cpp#include<iostream>#include<thread>#include<Windows.h>usingnamespacestd;voidthread1(){while(true){Sleep(1000);cout<<"thread1"<<endl;}}voidthread2(inta,intb){while(true){Sleep(500);cout<<"thread2:"<<a<<"+"<<b<<"="<<a+b<<endl;}}intmain(){std::threadt1(thread1);std::threadt2(thread2,1,2);if(t1.joinable())

t1.join();//L1t1加入阻塞main線程隊(duì)列if(t2.joinable())

t2.join();//L2

t2輸入阻塞main隊(duì)例,即讓main等t2優(yōu)先執(zhí)行Sleep(2000);return0;}程序運(yùn)行結(jié)果如下,thread2:1+2=3thread1thread2:1+2=3thread2:1+2=3thread1……

對比例9-2,程序不會異常中止。因線程死循環(huán),執(zhí)行永不停9.2.2

獲取線程ID線程ID線程被創(chuàng)建后,系統(tǒng)會為它分配一個線程ID。這個ID在整個操作系統(tǒng)范圍內(nèi)是唯一的,在程序中可以用線程ID來識別不同的線程。獲取方法用類this_thread中g(shù)et_id()方法或類threadget_id()方法,如下所示:this_thread::get_id(); //static成員函數(shù)thread::get_id(); //非static成員函數(shù)注意事項(xiàng)this_thread::get_id()是一個靜態(tài)成員函數(shù),用于獲取當(dāng)前線程的ID,不需要創(chuàng)建對象就可以通過類this_thread直接引用它。Thread::get_id()是一個非靜態(tài)成員函數(shù),必須創(chuàng)建類thread的對象后,通過對象才能夠引用它。get_id()獲取的線程ID是一個封裝好的類類型thread::id,可以用cout直接輸出,但不能直接作為整數(shù)使用。如果需要將id作為整數(shù)使用,可以先將其轉(zhuǎn)換成一個ostringstream類型的字符串輸出流對象,再將此對象轉(zhuǎn)換成字符串類型,最后才能夠?qū)⒃撟址D(zhuǎn)換成整數(shù)。【例9-4】設(shè)計(jì)線程thread1創(chuàng)建一個磁盤文件,并將自己的線程ID和一串字符寫入文件,創(chuàng)建線程thread2讀取thread1創(chuàng)建的文件內(nèi)容,用靜態(tài)和非靜態(tài)的get_id()獲取thread1的線程ID,并將主線程main的線程ID轉(zhuǎn)換為整數(shù)#include<iostream>#include<thread>#include<fstream>#include<sstream>usingnamespacestd;

voidthread1(stringfilename){ofstreamoutfile(filename); //L1,創(chuàng)建磁盤文件outfile<<this_thread::get_id()<<"\t" //L2,在文件中寫入線程ID和字符串<<"thread1writethisstring!"<<endl; cout<<"inthread1,ID:"<<this_thread::get_id()<<endl; //L3,輸出當(dāng)前線程的ID}intthread2(stringfilename){ifstreaminfile(filename); //L4,可打開thread1創(chuàng)建的文件chars[100]; cout<<"Iamthread2,thread1writethefollowingstring:"<<endl; //L5while(!infile.eof()){ //L6,讀出thread1建立的文件數(shù)據(jù)infile.getline(s,100); cout<<s<<endl; //L7,輸出文件中的內(nèi)容}return1;}

intmain(){threadt1(thread1,"D:\\abc.txt");//L8,創(chuàng)建t1線程對象,建立abc.txt文件threadt2(thread2,"D:\\abc.txt"); //L9,創(chuàng)建t2線程對象,讀取abc.txt文件cout<<"thread1ID:"<<t1.get_id()<<endl;//L10,獲取t1線程對象的線程IDif(t1.joinable())t1.join();if(t2.joinable())t2.join();cout<<"mainthreadID:"<<this_thread::get_id()<<endl; //L11,輸出主線程main的IDthread::idmid=std::this_thread::get_id(); //L12,獲取主線程main的IDostringstreamoss;oss<<mid; //L13,轉(zhuǎn)換主線程ID為字符串流對象std::stringstr=oss.str(); //L14,將ID對象轉(zhuǎn)換為字符串IDstd::cout<<"mainthreadID:"<<str<<std::endl;unsignedlonglongthreadid=std::stoull(str); //L15,將字符串ID轉(zhuǎn)換為數(shù)值型std::cout<<"mainthreadID:"<<threadid<<std::endl;return0;}執(zhí)行程序,其中的一個輸出結(jié)果如下:thread1ID:17608 //語句L10的輸出Iamthread2,thread1writethefollowingstring://語句L5的輸出17608thread1writethisstring! //語句L6、L7的輸出inthread1,ID:17608 //語句L3的輸出mainthreadID:18232mainthreadID:18232mainthreadID:182329.3類和線程類靜態(tài)與非靜態(tài)成員函數(shù)的形參區(qū)別classA{voidf1(inta,intb){…}staticvoidf2(inta,intb){…}}f1()和f2()被編譯器處理后,其原型變成了如下形式:voidf1(A*this,inta,intb);voidf2(inta,intb);C++線程對象的構(gòu)造參數(shù)要求threadt(func,p1,p2,…);如何向C++標(biāo)準(zhǔn)線程對象傳遞線程函數(shù)類非靜態(tài)成員函數(shù)作線程threadt(&A::f1,this,a,b)//非靜態(tài)成員函數(shù)要額外傳this指針類靜態(tài)成員函數(shù)作線程threadt(&A::f2,a,b)【例9-5】為類myThread設(shè)計(jì)線程成員函數(shù)Write(),將它的線程ID和一些字符串寫入磁盤文件,設(shè)計(jì)static線程成員函數(shù)Read()讀取并輸出Write()創(chuàng)建的磁盤文件內(nèi)容#include<fstream>#include<sstream>#include<thread>#include<iostream>usingnamespacestd;classmyThread{shared_ptr<thread>t1,t2;public:myThread(){t1=t2=nullptr;}~myThread(){}voidWrite(stringfilename){t1.reset(newthread(&myThread::thread1,this,filename));//L1if(t1->joinable())t1->join();}voidRead(stringfilename){t2.reset(newthread(&myThread::thread2,filename)); //L2if(t2->joinable())t2->join();}voidthread1(stringfilename){ofstreamoutfile(filename);outfile<<this_thread::get_id()<<"\t"<<"string1"<<endl;outfile<<this_thread::get_id()<<"\t"<<"string2"<<endl;outfile.close();}staticvoidthread2(stringfilename){ifstreaminfile(filename);chars[200];while(!infile.eof()){infile.getline(s,100);cout<<s<<endl;}}};intmain(){myThreadt;t.Write("D:\\abc.txt");t.Read("\D:\\abc.txt");return0;}執(zhí)行程序后,結(jié)果如下:10760string110760string2shared_ptr<thread>t1,t2;MyThread用智能指針管理對象,因此不需要deletet1,t2!9.4線程同步為什么要進(jìn)行線程同步假設(shè)多個線程需要操作同一資源,如讀寫同一個內(nèi)存變量,修改同一個磁盤文件,使用同一臺打印機(jī),如果不加控制就會產(chǎn)生錯誤,因此需要避免這樣的錯誤。線程同步是指在多個線程并發(fā)執(zhí)行時,保證它們按照一定的順序執(zhí)行以達(dá)到正確的結(jié)果。因此,多線程必須進(jìn)行線程同步設(shè)計(jì)。如何進(jìn)行線程同步其基本思想是,當(dāng)一個線程在對某內(nèi)存區(qū)域進(jìn)行寫操作時,其他線程都不可以對這個內(nèi)存區(qū)域進(jìn)行操作,需要等到該線程完成對該內(nèi)存區(qū)域的寫操作并釋放對它的控制后,其他線程才能夠?qū)υ搩?nèi)存區(qū)域進(jìn)行操作;如果所有線程執(zhí)行的都是讀操作,就可以同時執(zhí)行線程同步的方法則是用互斥鎖、信號量、條件變量、讀寫鎖等技術(shù)對多線程共用的內(nèi)存區(qū)域加以保護(hù)和使用控制,以避免多線程訪問時所產(chǎn)生的沖突問題。9.4.1互斥鎖互斥鎖:mutexMutex的作用是對多線程共同訪問的資源進(jìn)行保護(hù)。主要成員如下:mutex::lock();mutex::unlock();mutex::try_lock();一個mutex在同一時刻最多只能屬于一個線程,獲取mutex的線程就成為它的擁有者,可以對mutex實(shí)施lock操作。等到該線程執(zhí)行unlock操作后,其他線程才能獲得該mutex。C++11標(biāo)準(zhǔn)中的其它常用互斥鎖互斥鎖類型標(biāo)準(zhǔn)說

明mutexC++11基本互斥鎖timed_mutex C++11有限時機(jī)制的互斥鎖recursive_mutex C++11能被同一線程遞歸鎖定的互斥鎖recursive_timed_mutexC++11timed_mutex和recursive_mutex雙重特點(diǎn)的互斥鎖shared_mutexC++17共享互斥鎖shared_timed_mutexC++14有限時機(jī)制的共享互斥鎖【例9-6】設(shè)計(jì)一個搶占教室座位號的程序,假設(shè)在3秒內(nèi),每名學(xué)生每次只可以占1個座位,但可以占座3次。#include<iostream>#include<windows.h>#include<thread>usingnamespacestd;intseatnum=0;voidoccuSeat(stringname){for(inti=0;i<3;i++){++seatnum;cout<<name<<"搶占了座位號:"<<seatnum<<endl;Sleep(3000);}}intmain(){threadt1(occuSeat,"張三");threadt2(occuSeat,"李四");t1.join();t2.join();return0;}線程函數(shù)occuSeat(

)模仿學(xué)生占座位的行為,它以學(xué)生姓名為參數(shù),每調(diào)用一次函數(shù)就表示某學(xué)生的一次搶占座位行動。程序運(yùn)行結(jié)果如下:李四搶占了座位號:

張三搶占了座位號:22張三搶占了座位號:4李四搶占了座位號:4李四張三搶占了座位號:6搶占了座位號:6同一座位被多次搶占!例9-7用互斥鎖解決此問題!【例9-7】設(shè)計(jì)一個搶占教室座位號的程序,假設(shè)在3秒內(nèi),每名學(xué)生每次只可以占1個座位,可以占座3次,但同一次座位不允許被多次搶占。#include<mutex>#include<thread>#include<iostream>#include<windows.h>usingnamespacestd;intseatnum=0;mutexseatmux;//L1,定義互斥鎖voidoccuSeat(stringname){for(inti=0;i<3;i++) {

seatmux.lock();//L2,鎖住互斥鎖

++seatnum;cout<<name<<"搶占了座位號:"<<seatnum<<endl;

seatmux.unlock();//L3,釋放互斥鎖

Sleep(3000);//L4,等待3秒鐘

}}intmain(){threadt1(occuSeat,"張三");threadt2(occuSeat,"李四");t1.join();t2.join();return0;}程序運(yùn)行結(jié)果:張三搶占了座位號:1李四搶占了座位號:2李四搶占了座位號:3張三搶占了座位號:4張三搶占了座位號:5李四搶占了座位號:69.4.2讀寫鎖C++17讀寫鎖:shared_mutexshared_mutex也稱為“共享–獨(dú)占鎖”,允許多個線程同時以讀模式加鎖,但只允許一個線程以寫模式加鎖,并且讀時不允許寫、寫時不允許讀。mutex只有加鎖或者不加鎖兩種狀態(tài)互斥鎖相比較,而且一次只允許一個線程加鎖。顯然shared_mutex允許線程具有更高的并發(fā)性。Shared_mutex用法shared_mutex鎖有讀模式加鎖、寫模式加鎖和不加鎖三種狀態(tài)。由unique_lock(獨(dú)占鎖)和shared_lock(共享鎖)兩個對象來配合使用。某線程已經(jīng)通過unique_lock獲得了shared_mutex鎖,那么其他線程就不能獲得該鎖,嘗試獲得此鎖(讀模式或?qū)懩J剑┑木€程會被阻塞;當(dāng)沒有任何線程獲得獨(dú)占鎖時,其他線程才能用shared_lock獲得shared_mutex鎖。此外,每個線程在同一時刻只能獲得shared_mutex共享鎖或獨(dú)占鎖中的一個unique_lock和shared_lock對象在被定義時會自動調(diào)用構(gòu)造函數(shù)對shared_mutex加鎖,在對象失去作用域時會自動調(diào)用析構(gòu)函數(shù)對其鎖住的shared_mutex對象解鎖,在不需要可使用unlock操作對其鎖住的shared_mutex對象解鎖。【例9-8】用shared_mutex設(shè)計(jì)讀寫數(shù)據(jù)的線程,實(shí)現(xiàn)對同一內(nèi)存數(shù)據(jù)的寫入和同時讀取功能。#include<iostream>#include<thread>#include<shared_mutex>usingnamespacestd;shared_mutexrwlock; //L1,定義讀寫鎖intsharedata=0;//L2,共享內(nèi)存區(qū)域voidreadData(stringtname){//L3,讀線程函數(shù)

for(inti=0;i<12;i++){shared_lock<shared_mutex>rlock(rwlock);//L4,對rwlock申請讀鎖

cout<<tname<<"\tdata="<<sharedata<<endl;} //L5,自動釋放rwlock的讀鎖}voidwriteData(stringtname){ /L6,寫線程函數(shù)

for(inti=0;i<5;i++){unique_lock<shared_mutex>wlock(rwlock);//L7,對rwlock申請寫鎖

sharedata++;cout<<tname<<"\tdata="<<sharedata<<endl;} //L8,自動釋放rwlock的寫鎖}intmain(){threadw1(writeData,"w1");threadr1(readData,"r1");threadr2(readData,"r2");threadr3(readData,"r3");r1.join();r2.join();r3.join();w1.join();return0;}某次運(yùn)行的部分輸出如下:w1data=1r1data=1r2data=1r3data=1r2data=1r1data=1r1data=1r1r3r2data=data=data=111

r3data=1r1data=r21data=1

w1data=2r3data=2r1r2data=data=22……9.4.3信號量c++20信號量的概念和功能信號量本質(zhì)上是一個非負(fù)的整數(shù)計(jì)數(shù)器,具有P、V兩種操作,一次P操作使信號量減1,一次V操作使信號量加1。主要用來控制多線程(進(jìn)程)對公共資源的訪問,限制并發(fā)訪問共享資源的線程數(shù)量,被廣泛應(yīng)用于線程(進(jìn)程)之間的同步和互斥控制中。其控制方法是,當(dāng)信號量值大于0時,線程被執(zhí)行,否則被阻塞(線程被掛起,直到信號量大于0)。C++標(biāo)準(zhǔn)中信號類的類型類型主要操作說

明counting_semaphorebinary_semaphoreacquire()執(zhí)行p操作。若信號量大于0,則減少1,線程繼續(xù);否則阻塞線程,直到信號量大于0再喚醒繼續(xù)執(zhí)行release(n)執(zhí)行v操作,信號量加n(省略n,則加1)9.4.3信號量c++203.信號量的用法包含頭文件,必須是支持C++20標(biāo)準(zhǔn)及之后的C++編譯器版本#include<semaphore>定義信號量:counting_semaphore<N>csem(信號量初始數(shù)量);//N是信號量上限值counting_semaphorecsem(信號量初始數(shù)量);//信號量無上限值binary_semaphorebsem(初值);//初值只能夠是0或1【例9-9】設(shè)計(jì)1個線程函數(shù),可以通過信號量同時啟動5個線程,最多10線程。//Eg9-9.cpp#include<iostream>#include<semaphore>#include<thread>usingnamespacestd;counting_semaphore<10>csem(5);//L1intcakeNumber=0;binary_semaphorebsem(0);//示例二值信號量的定義方法voidmakeCake(stringthreadname){

csem.acquire(); //L2cout<<threadname<<"\tmakeCake“<<++cakeNumber<<endl;//csem.release(); //L3}intmain(){cout<<"main:readytosignal:release\n";threadt1=thread(makeCake,"bakeA:");//L4threadt2=thread(makeCake,"bakeB:");//L5threadt3=thread(makeCake,"bakeC:");//L6cout<<"main:signalend\n";

t1.join();t2.join();t3.join();return0;}程序運(yùn)行結(jié)果如下:main:readytosignal:releasemain:signalendbakeC:makeCakebakeA:makeCake2bakeB:makeCake3

1L1定義了信號量csem為5,L2使信號量減1,L3處的信號量加1操作被注釋,因此線程未釋放資源。t1、t2、t3每執(zhí)行一次,csem的信號量減1,因?yàn)槌跏贾禐?,減3之后仍然大于0,所以程序正常運(yùn)行。從輸出結(jié)果的第三行可以看到,在同一輸出行中有3個線程的交叉輸出,表明它們是并發(fā)執(zhí)行的?!纠?-10】信號量小于0時,線程被阻塞。在例9-9中,將L1語句中csem信號量的初值改為2,即修改成如下語句:counting_semaphore<10>csem(2);3個線程并發(fā)執(zhí)行將使信號小于0,線程被阻塞,運(yùn)行結(jié)果如下:無t2的輸出【例9-11】信號量充足,阻塞線程被激活。在例9-10中,在主線程main中增加csem信號量,保障線程t1、t2、t3有充足的資源得以執(zhí)行完成。

修改后的main()函數(shù)如下,其余程序代碼不作修改。//Eg9-11.cpp……intmain(){cout<<"main:readytosignal:release\n";threadt1=thread(makeCake,"bakeA:");threadt2=thread(makeCake,"bakeB:");threadt3=thread(makeCake,"bakeC:");cout<<"main:signalend\n";csem.release(3);//L3釋放資源,csem信號量加3t1.join();t2.join();t3.join();retrun0;}在例9-10中,初始信號量csem為2,t1,t2,t3三個線程對象中的線程執(zhí)行將使信號量小于0,導(dǎo)到某個線程被阻塞。本例在例9-10的main主線程中,語句L3增加了3個信號量,從上面的輸出結(jié)果可以分析出:3個線程都正常執(zhí)行了!4.典型應(yīng)用1:通過信號量,控制線程順序執(zhí)行【例9-12】設(shè)計(jì)線程函數(shù),通過信號量控制多個線程依次執(zhí)行(線性執(zhí)行,無并發(fā)性)。解題思路:修改前面的程序,只需將信號量csem的初值設(shè)置為1,同時當(dāng)線程獲得資源并使用后,就立即釋放它使信號量加1。9.4.3信號量c++20#include<iostream>#include<semaphore>#include<thread>usingnamespacestd;

counting_semaphorecsem(1);

//L1信號量初值為1intcakeNumber=0;voidmakeCake(stringthreadname){

csem.acquire();

//L2信號量減1cout<<threadname<<"\tmakeCake"<<++cakeNumber<<endl;

csem.release();

//L3信號量加1}intmain(){cout<<"main:readytosignal:release\n";threadt1=thread(makeCake,"bakeA:"); //L4threadt2=thread(makeCake,"bakeB:"); //L5threadt3=thread(makeCake,"bakeC:"); //L6cout<<"main:signalend\n";

t1.join();t2.join();t3.join();return0;}程序運(yùn)行結(jié)果如下:main:readytosignal:releasemain:signalendbakeB:makeCake1bakeA:makeCake2bakeC:makeCake3由于信號量1,1次只有一個線程能夠獲取信號csem,當(dāng)其獲取后執(zhí)行了csem.acquire()操作,信號變?yōu)?,至使其它線程被阻塞。線程執(zhí)行完成后,通過csem.release()操作使信號量加1,變?yōu)?.某個線程再次獲取信號量,得以執(zhí)行4.典型應(yīng)用2:通過信號量建立生產(chǎn)者-消費(fèi)者線程模型可以用信號量控制多線程模仿于生產(chǎn)者—消費(fèi)者模型。信號量代表產(chǎn)品數(shù)量,當(dāng)生產(chǎn)者線程(增加信號量的線程)完成產(chǎn)品生產(chǎn)后,調(diào)用release()操作增加信號量,表示資源數(shù)量增加,可以根據(jù)當(dāng)前資源的數(shù)量按需要喚醒指定數(shù)量的資源消費(fèi)者線程(執(zhí)行acquire減少信號量的線程)。資源消費(fèi)者線程一旦獲得信號量,就會減少資源數(shù)量,如果資源數(shù)量減少到0,那么消費(fèi)者線程將全部處于阻塞狀態(tài);當(dāng)有新資源到來時,消費(fèi)者線程將繼續(xù)被喚醒。9.4.3信號量c++20【例9-13】3個面包師,每次烤1個面包;2個顧客,每次消費(fèi)1個面包。設(shè)計(jì)線程,模仿這個過程。設(shè)計(jì)思路:設(shè)計(jì)代表面包編號的全局變量cakeNumber,每烤1個面包就讓cakeNumber加1,每消費(fèi)1個面包就減1;設(shè)計(jì)一個counting_semaphore類型的信號量、一個代表生產(chǎn)面包的線程函數(shù)makeCake()和一個代表消費(fèi)面包的線程函數(shù)consumerCake();每生產(chǎn)1個面包就調(diào)用release操作,讓信號量加1;每消費(fèi)1個面包就調(diào)用acquire操作,讓信號量減1。9.4.3信號量c++209.4.3信號量c++20//Eg9-13.cpp#include<iostream>#include<semaphore>#include<thread>usingnamespacestd;counting_semaphorecsem(1);//信號量初始為1,也可為0intcakeNumber=0;voidmakeCake(stringthreadname){cout<<threadname<<":makeCake"<<++cakeNumber<<"\t"<<endl;

csem.release();//信號量加1}voidconsumerCake(stringthreadname){

csem.acquire();//信號量減1cout<<threadname<<"consumerCake"<<cakeNumber--<<"\t"<<endl;}intmain(){cout<<"main:readytosignal:release\n";threadt1=thread(makeCake,"bakeA:");threadt2=thread(makeCake,"bakeB:");threadt3=thread(makeCake,"bakeC:"); threadt4=thread(consumerCake,"customer1:");threadt5=thread(consumerCake,"customer2:"); cout<<"main:signalend\n";

t1.join();t2.join();t3.join();t4.join();t5.join();}每次結(jié)果不盡相同,某次結(jié)果如下:main:readytosignal:releasemain:signalendbakeA::makeCake1bakeC::makeCake2bakeB::makeCake3customer1:consumerCake3customer2:consumerCake29.4.4條件變量為什么會用條件變量在線程設(shè)計(jì)中存在這樣一種業(yè)務(wù)邏輯,就是線程A通過無限次循環(huán)檢測某個條件的成立,如果條件不成立就一直檢測,直到條件成立時才能夠執(zhí)行其他業(yè)務(wù)邏輯,而條件是由另一個線程B修改的。兩個線程共同應(yīng)用的條件可以用信號量來抽象,如圖所示。線程A存在較大的效率問題!可能存在這樣一種情況:信號不大于0,但在它剛好釋放互斥鎖之后,線程B增加了信號量,它也會睡眠n秒鐘,等到下次進(jìn)行條件檢測時才會退出循環(huán)。條件變量就是用于解決線程A的效率問題的:9.4.4條件變量條件變量工作原理如果線程A在檢查到條件不滿足(信號量不大于0)時,就立即釋放互斥鎖并處于等待狀態(tài)(在圖中的wait處等待);線程B在增加了信號量后(圖中的notify())就主動通知線程A,并讓出互斥鎖,A就不用在每次條件不符合時都等待n秒鐘,而是條件一旦滿足就馬上執(zhí)行,最大限度地提高運(yùn)行效率。9.4.4條件變量3.條件變量編程方法#include<condition_variable>應(yīng)用condition_variable或condition_variable_any條件變量的成員函數(shù)wait()和notify()與互斥鎖配合實(shí)現(xiàn)對線程的條件控制:wait(unique_lock<mutex>&lck)(1)wait(unique_lock<mutex>&lck,Predicatepred)(2)notify_one()notify_all()//通知全部函數(shù)wait()的功能是阻塞當(dāng)前線程,直到收到notify()的通知為止。在第(2)種用法中,當(dāng)pred=false時,阻塞線程;當(dāng)pred=true時,不阻塞線程。wait()可依次拆分為三個操作步驟:①釋放互斥鎖(lck);②等待在條件變量上;③再次獲取互斥鎖(當(dāng)條件變量接到notify()的通知時,將再次獲取lcx)。notify_one()只喚醒一個線程,不存在鎖爭用問題,所以被喚醒的線程能夠立即獲得鎖。其余線程則會繼續(xù)被阻塞,等待再次被notify_one()或者notify_all()喚醒。notify_all()會喚醒所有被阻塞的線程,因此存在鎖的爭用,只有一個線程能夠獲得鎖,其余未獲得鎖的線程會繼續(xù)被阻塞,當(dāng)持有鎖的線程釋放鎖時,這些線程會繼續(xù)嘗試獲得鎖。9.4.4條件變量【例9-14】設(shè)計(jì)一個面包銷售的生產(chǎn)者–消費(fèi)者程序。生產(chǎn)者不斷地生產(chǎn)面包并放入銷售隊(duì)列中,消費(fèi)者從隊(duì)列中取出面包,并執(zhí)行面包銷售任務(wù)。設(shè)計(jì)思路:在生產(chǎn)者–消費(fèi)者模型中,通常由生產(chǎn)者產(chǎn)生任務(wù)后將其放入任務(wù)隊(duì)列,然后通知消費(fèi)者從任務(wù)隊(duì)列中取出一個任務(wù)并予以執(zhí)行。利用條件變量和互斥鎖,可以便捷地實(shí)現(xiàn)這類程序模型。方法:讓生產(chǎn)者線程和消費(fèi)者線程通過互斥鎖共同維護(hù)任務(wù)隊(duì)列,實(shí)現(xiàn)兩個線程對任務(wù)隊(duì)列的異步訪問,即:生產(chǎn)者線程獲取互斥鎖后將創(chuàng)建的任務(wù)放入任務(wù)隊(duì)列,然后釋放互斥鎖并通知消費(fèi)者線程到

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論