溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

C++11中的線程庫是什么

發(fā)布時(shí)間:2022-03-04 09:54:26 來源:億速云 閱讀:131 作者:小新 欄目:開發(fā)技術(shù)

小編給大家分享一下C++11中的線程庫是什么,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

    一、線程庫的介紹

    在C++11之前,涉及到多線程問題,都是和平臺(tái)相關(guān)的,比如windows和linux下各有自己的接口,這使得代碼的可移植性比較差。C++11中最重要的特性就是對(duì)線程進(jìn)行支持了,使得C++在并行編程時(shí)不需要依賴第三方庫,而且在原子操作中還引入了原子類的概念。要使用標(biāo)準(zhǔn)庫中的線程,必須包含< thread >頭文件。

    特點(diǎn):跨平臺(tái)、面向?qū)ο蠓庋b的類(每個(gè)線程是個(gè)類對(duì)象)。

    其實(shí)現(xiàn)原理是封裝庫時(shí)使用了條件編譯,也就是說他的底層還是分別調(diào)用了不同平臺(tái)的線程API。

    函數(shù)名功能
    thread()構(gòu)造一個(gè)線程對(duì)象,沒有關(guān)聯(lián)任何線程函數(shù),即沒有啟動(dòng)任何線程
    thread(fn,args1, args2,&hellip;)構(gòu)造一個(gè)線程對(duì)象,并關(guān)聯(lián)線程函數(shù)fn,args1,args2,&hellip;為線程函數(shù)的參數(shù)
    get_id()獲取線程id
    jionable()線程是否還在執(zhí)行,joinable代表的是一個(gè)正在執(zhí)行中的線程。
    jion()該函數(shù)調(diào)用后會(huì)阻塞住線程,當(dāng)該線程結(jié)束后,主線程繼續(xù)執(zhí)行
    detach()在創(chuàng)建線程對(duì)象后馬上調(diào)用,用于把被創(chuàng)建線程與線程對(duì)象分離開,分離的線程變?yōu)楹笈_(tái)線程,創(chuàng)建的線程的"死活"就與主線程無關(guān)

    1.1. 使用時(shí)的注意點(diǎn)

    線程是操作系統(tǒng)中的一個(gè)概念,線程對(duì)象可以關(guān)聯(lián)一個(gè)線程,用來控制線程以及獲取線程的狀態(tài)

    當(dāng)創(chuàng)建一個(gè)線程對(duì)象后,沒有提供線程函數(shù),該對(duì)象實(shí)際沒有對(duì)應(yīng)任何線程。

    #include <thread>
    int main()
    {
     std::thread t1;
     cout << t1.get_id() << endl;//0
     return 0; }

    get_id()的返回值類型為id類型,id類型實(shí)際為std::thread命名空間下封裝的一個(gè)類,該類中包含了一個(gè)結(jié)構(gòu)體:

    // vs下查看
    typedef struct
    { /* thread identifier for Win32 */
     void *_Hnd; /* Win32 HANDLE */
     unsigned int _Id;
    } _Thrd_imp_t;

    當(dāng)創(chuàng)建一個(gè)線程對(duì)象后,并且給線程關(guān)聯(lián)線程函數(shù),該線程就被啟動(dòng),與主線程一起運(yùn)行。線程函數(shù)一般情況下可按照以下三種方式提供:函數(shù)指針、lambda表達(dá)式、函數(shù)對(duì)象

    #include <iostream>
    using namespace std;
    #include <thread>
    void ThreadFunc(int a) {
     cout << "Thread1" << a << endl; }
    class TF
    {
    public:
     void operator()()
     {
     cout << "Thread3" << endl;
     }
    };
    int main()
     // 線程函數(shù)為函數(shù)指針
     thread t1(ThreadFunc, 10);
     
     // 線程函數(shù)為lambda表達(dá)式
     thread t2([]{cout << "Thread2" << endl; });
     // 線程函數(shù)為函數(shù)對(duì)象
     TF tf;
     thread t3(tf);
     t1.join();
     t2.join();
     t3.join();
     cout << "Main thread!" << endl;
     return 0; }

    4.thread類是防拷貝的,不允許拷貝構(gòu)造以及賦值,但是可以移動(dòng)構(gòu)造和移動(dòng)賦值,即將一個(gè)線程對(duì)象關(guān)聯(lián)線程的狀態(tài)轉(zhuǎn)移給其他線程對(duì)象,轉(zhuǎn)移期間不意向線程的執(zhí)行

    5.可以通過jionable()函數(shù)判斷線程是否是有效的,如果是以下任意情況,則線程無效:

    • 采用無參構(gòu)造函數(shù)構(gòu)造的線程對(duì)象(沒有提供線程函數(shù),即這個(gè)線程本身就是無效的)

    • 線程對(duì)象的狀態(tài)已經(jīng)轉(zhuǎn)移給其他線程對(duì)象(資源被轉(zhuǎn)移)

    • 線程已經(jīng)調(diào)用jion或者detach結(jié)束(線程已結(jié)束,或者已經(jīng)分離)

    1.2. 線程函數(shù)參數(shù)

    線程函數(shù)的參數(shù)是以值拷貝的方式拷貝到線程??臻g中的,因此:即使線程參數(shù)為引用類型,在線程中修改后也不能修改外部實(shí)參,因?yàn)槠鋵?shí)際引用的是線程棧中的拷貝,而不是外部實(shí)參。

    C++11中的線程庫是什么

    C++11中的線程庫是什么

    注意:如果是類成員函數(shù)作為線程參數(shù)時(shí),必須將this作為線程函數(shù)參數(shù)

    C++11中的線程庫是什么

    1.3. join與detach

    啟動(dòng)了一個(gè)線程后,當(dāng)這個(gè)線程結(jié)束的時(shí)候,如何去回收線程所使用的資源呢?thread庫給我們兩種選擇:

    join()方式
    join():主線程被阻塞,當(dāng)新線程終止時(shí),join()會(huì)清理相關(guān)的線程資源,然后返回,主線程再繼續(xù)向下執(zhí)行,然后銷毀線程對(duì)象。由于join()清理了線程的相關(guān)資源,thread對(duì)象與已銷毀的線程就沒有關(guān)系了,因此一個(gè)線程對(duì)象只能使用一次join(),否則程序會(huì)崩潰。
    采用jion()方式結(jié)束線程時(shí),jion()的調(diào)用位置非常關(guān)鍵。為了避免該問題,可以采用RAII的方式對(duì)線程對(duì)象進(jìn)行封裝,RAII :利用對(duì)象聲明周期來管理線程資源,保證線程資源正常銷毀,資源獲取立即初始化,在構(gòu)造函數(shù)中初始化資源,在析構(gòu)函數(shù)中銷毀資源。 比如:

    #include <thread>
    class mythread
    {
    public:
     explicit mythread(std::thread &t) :m_t(t){}
     ~mythread()
     {
     if (m_t.joinable())//判斷線程是否還在
     m_t.join();
     }
     mythread(mythread const&)=delete;
     mythread& operator=(const mythread &)=delete;
    private:
     std::thread &m_t;
    };
    void ThreadFunc() { cout << "ThreadFunc()" << endl; }
    bool DoSomething() { return false; }
    int main()
    {
     thread t(ThreadFunc);
     mythread q(t);
     if (DoSomething())
     return -1;
     return 0; 
    }

    detach()方式
    detach():該函數(shù)被調(diào)用后,新線程與線程對(duì)象分離,不再被線程對(duì)象所表達(dá),就不能通過線程對(duì)象控制線程了,新線程會(huì)在后臺(tái)運(yùn)行,其所有權(quán)和控制權(quán)將會(huì)交給c++運(yùn)行庫。同時(shí),C++運(yùn)行庫保證,當(dāng)線程退出時(shí),其相關(guān)資源的能夠正確的回收。

    注意:線程對(duì)象銷毀前,要么以jion()的方式等待線程結(jié)束,要么以detach()的方式將線程與線程對(duì)象分離。

    二、原子性操作庫

    多線程最主要的問題是共享數(shù)據(jù)帶來的問題(即線程安全)。如果共享數(shù)據(jù)都是只讀的,那么沒問題,因?yàn)?strong>只讀操作不會(huì)影響到數(shù)據(jù),更不會(huì)涉及對(duì)數(shù)據(jù)的修改,所以所有線程都會(huì)獲得同樣的數(shù)據(jù)。但是,當(dāng)一個(gè)或多個(gè)線程要修改共享數(shù)據(jù)時(shí),就會(huì)產(chǎn)生很多潛在的麻煩。

    2.1. atomic

    雖然加鎖可以解決,但是加鎖有一個(gè)缺陷就是:只要一個(gè)線程在對(duì)sum++時(shí),其他線程就會(huì)被阻塞,會(huì)影響程序運(yùn)行的效率,而且鎖如果控制不好,還容易造成死鎖。
    因此C++11中引入了原子操作。所謂原子操作:即不可被中斷的一個(gè)或一系列操作,C++11引入的原子操作類型,使得線程間數(shù)據(jù)的同步變得非常高效。

    C++11中的線程庫是什么

    注意:原子類型通常屬于"資源型"數(shù)據(jù),多個(gè)線程只能訪問單個(gè)原子類型的拷貝,因此在C++11中,原子類型只能從其模板參數(shù)中進(jìn)行構(gòu)造,不允許原子類型進(jìn)行拷貝構(gòu)造、移動(dòng)構(gòu)造以及operator=等,為了防止意外,標(biāo)準(zhǔn)庫已經(jīng)將atmoic模板類中的拷貝構(gòu)造、移動(dòng)構(gòu)造、賦值運(yùn)算符重載默認(rèn)刪除掉了。

    #include<thread>
    #include<atomic>
    atomic<int>x = 0;
    void Add(int n)
    {
    
    	for (int i = 0; i < n; i++)
    	{
    		x++;
    	}
    }
    int main()
    	thread t1(Add, 10000);
    	thread t2(Add, 10000);
    	t1.join();
    	t2.join();
    	cout << x << endl;//20000
    	return 0;

    2.2. 鎖

    在多線程環(huán)境下,如果想要保證某個(gè)變量的安全性,只要將其設(shè)置成對(duì)應(yīng)的原子類型即可,即高效又不容易出現(xiàn)死鎖問題。但是有些情況下,我們可能需要保證一段代碼的安全性,那么就只能通過鎖的方式來進(jìn)行控制。

    1.lock、unlock:

    線程函數(shù)調(diào)用lock()時(shí),可能會(huì)發(fā)生以下三種情況:

    • 如果該互斥量當(dāng)前沒有被鎖住,則調(diào)用線程將該互斥量鎖住,直到調(diào)用 unlock之前,該線程一直擁有該鎖。

    • 如果當(dāng)前互斥量被其他線程鎖住,則當(dāng)前的調(diào)用線程被阻塞住。

    • 如果當(dāng)前互斥量被當(dāng)前調(diào)用線程鎖住,則會(huì)產(chǎn)生死鎖(同一線程,不可以連續(xù)調(diào)用兩次鎖)。

    線程函數(shù)調(diào)用try_lock()時(shí),可能會(huì)發(fā)生以下三種情況:

    • 如果當(dāng)前互斥量沒有被其他線程占有,則該線程鎖住互斥量,直到該線程調(diào)用 unlock 釋放互斥量。

    • 如果當(dāng)前互斥量被其他線程鎖住,則當(dāng)前調(diào)用線程返回 false,而并不會(huì)被阻塞掉(非阻塞式)。

    • 如果當(dāng)前互斥量被當(dāng)前調(diào)用線程鎖住,則會(huì)產(chǎn)生死鎖。

    2.recursive_mutex:
    允許同一個(gè)線程對(duì)互斥量多次上鎖(即遞歸上鎖),來獲得對(duì)互斥量對(duì)象的多層所有權(quán),釋放互斥量時(shí)需要調(diào)用與該鎖層次深度相同次數(shù)的 unlock(),除此之外,recursive_mutex 的特性和mutex 大致相同。

    C++11中的線程庫是什么

    3.timed_mutex:
    比 mutex 多了兩個(gè)成員函數(shù),try_lock_for(),try_lock_until()

    try_lock_for()
    接受一個(gè)時(shí)間范圍,表示在這一段時(shí)間范圍之內(nèi)線程如果沒有獲得鎖則被阻塞?。ㄅc std::mutex的 try_lock() 不同,try_lock 如果被調(diào)用時(shí)沒有獲得鎖則直接返回 false),如果在此期間其他線程釋放了鎖,則該線程可以獲得對(duì)互斥量的鎖,如果超時(shí)(即在指定時(shí)間內(nèi)還是沒有獲得鎖),則返回 false。

    try_lock_until()
    接受一個(gè)時(shí)間點(diǎn)作為參數(shù),在指定時(shí)間點(diǎn)未到來之前線程如果沒有獲得鎖則被阻塞住,如果在此期間其他線程釋放了鎖,則該線程可以獲得對(duì)互斥量的鎖,如果超時(shí)(即在指定時(shí)間內(nèi)還是沒有獲得鎖),則返回 false。

    int sum = 0;
    
    timed_mutex Lock;
    chrono::milliseconds timeout(100);//100毫秒
    void func(int number)
    {
    	for (int i = 0; i < number; i++)
    	{
    		
    		//Lock.try_lock_for(timeout);//接收一個(gè)時(shí)間范圍
    		Lock.try_lock_until(chrono::steady_clock::now() + timeout);//接收一個(gè)時(shí)間點(diǎn)
    		sum++;
    		Lock.unlock();
    	}
    }

    4.recursive_timed_mutex
    是recursive_mutex、timed_mutex兩種鎖的結(jié)合。

    5.lock_guard(守衛(wèi)鎖):
    加鎖和解鎖的過程,通過類的構(gòu)造和析構(gòu)來自動(dòng)控制加鎖和釋放鎖(RAII思想)

    template<class _Mutex>
    class lock_guard
    {
    public:
    
    	// 構(gòu)造函數(shù)加鎖
    	explicit lock_guard(_Mutex& _Mtx)
    		: _MyMutex(_Mtx)
    	{
    		_MyMutex.lock();
    	}
    	//通過析構(gòu)函數(shù)釋放鎖
    	~lock_guard() _NOEXCEPT
    		_MyMutex.unlock();
    	//防拷貝和防賦值
    	lock_guard(const lock_guard&) = delete;
    	lock_guard& operator=(const lock_guard&) = delete;
    private:
    	_Mutex& _MyMutex;
    };
    int sum = 0;
    mutex Lock;
    void func(int number)
    {
    	for (int i = 0; i < number; i++)
    	{
    		lock_guard<mutex> lg(Lock);
    		sum++;
    	}
    }

    lock_guard類模板主要是通過RAII的方式,對(duì)其管理的互斥量進(jìn)行了封裝,在需要
    加鎖的地方,只需要用上述介紹的任意互斥體實(shí)例化一個(gè)lock_guard,調(diào)用構(gòu)造函數(shù)成功上鎖,出作用域前,lock_guard對(duì)象要被銷毀,調(diào)用析構(gòu)函數(shù)自動(dòng)解鎖,可以有效避免死鎖問題。
    lock_guard的缺陷:太單一,用戶沒有辦法對(duì)該鎖進(jìn)行控制,因此C++11又提供了unique_lock。

    6.unique_lock

    與lock_gard類似,unique_lock類模板也是采用RAII的方式對(duì)鎖進(jìn)行了封裝,并且也是以獨(dú)占所有權(quán)的方式管理mutex對(duì)象的上鎖和解鎖操作,即其對(duì)象之間不能發(fā)生拷貝。在構(gòu)造(或移動(dòng)(move)賦值)時(shí),unique_lock 對(duì)象需要傳遞一個(gè) Mutex 對(duì)象作為它的參數(shù),新創(chuàng)建的 unique_lock 對(duì)象負(fù)責(zé)傳入的 Mutex對(duì)象的上鎖和解鎖操作。使用以上類型互斥量實(shí)例化unique_lock的對(duì)象時(shí),自動(dòng)調(diào)用構(gòu)造函數(shù)上鎖,unique_lock對(duì)象銷毀時(shí)自動(dòng)調(diào)用析構(gòu)函數(shù)解鎖,可以很方便的防止死鎖問題。 與lock_guard不同的是,unique_lock更加的靈活,提供了更多的成員函數(shù):

    • 上鎖/解鎖操作:lock、try_lock、try_lock_for、try_lock_until和unlock

    • 修改操作:移動(dòng)賦值、交換(swap:與另一個(gè)unique_lock對(duì)象互換所管理的互斥量所有權(quán))、釋放(release:返回它所管理的互斥量對(duì)象的指針,并釋放所有權(quán))

    • 獲取屬性:owns_lock(返回當(dāng)前對(duì)象是否上了鎖)、operator bool()(與owns_lock()的功能相同)、mutex(返回當(dāng)前unique_lock所管理的互斥量的指針)。

    三、使用lambda表達(dá)式創(chuàng)建多個(gè)線程

    #include<iostream>
    #include<thread>
    #include<atomic>
    #include<vector>
    using namespace std;
    int main()
    {
    
    	atomic<int>x = 0;
    	//m個(gè)線程對(duì)x加n次
    	int m, n;
    	cin >> m >> n;
    	vector<thread>vthreads;
    	for (int i = 0; i < m; i++)
    	{
    		vthreads.push_back(thread([&x](int count) {
    			for (int i = 0; i < count; i++)
    			{
    				x++;
    			}
    			}, n));
    	}
    	for (auto& t : vthreads)
    		cout << t.get_id() <<".join()" << endl;
    		t.join();
    	cout << x << endl;
    	return 0;
    }

    C++11中的線程庫是什么

    四、條件變量

    C++11中的線程庫是什么

    需要注意的是,傳入wait的鎖不能是lock_guard,這是因?yàn)閘ock_guard沒有解鎖接口

    C++11中的線程庫是什么

    喚醒:

    Notify one -> 喚醒一個(gè)
    notify_all -> 喚醒一批

    #include<iostream>
    #include<thread>
    #include<mutex>
    #include<condition_variable>
    using namespace std;
    
    int main()
    {
    	int n = 20;
    	mutex mtx1,mtx2;
    	unique_lock<mutex> t;
    	condition_variable cv1,cv2;
    	//使用兩個(gè)線程打印0-n的數(shù),一個(gè)打印奇數(shù),一個(gè)打印偶數(shù)
    	thread t1([&]() 
    		{
    			for (int i = 0; i < n; i+=2)
    			{
    			
    				cout << this_thread::get_id() << ":" << i << endl;
    				cv2.notify_one();//打印偶數(shù)以后通知t2
    				unique_lock<mutex> lock(mtx1);
    				cv1.wait(lock);
    			}
    		});
    	thread t2([&]() 
    		{
    			for (int i = 1; i < n; i+=2)
    			{
    				unique_lock<mutex> lock(mtx2);
    				cv2.wait(lock);
    				cout << this_thread::get_id() << ":" << i << endl;
    				cv1.notify_one();//t2打印奇數(shù)以后,通知t1
    			}
    		});
    	t1.join();
    	t2.join();
    	return 0;
    }

    C++11中的線程庫是什么

    以上是“C++11中的線程庫是什么”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    c++
    AI