欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解C++11中的線程庫

 更新時間:2022年01月27日 15:04:52   作者:精致的灰(>_<)  
線程是操作系統(tǒng)中的一個概念,線程對象可以關聯一個線程,用來控制線程以及獲取線程的狀態(tài),這篇文章主要介紹了C++11中的線程庫的相關知識,需要的朋友可以參考下

一、線程庫的介紹

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

特點:跨平臺、面向對象封裝的類(每個線程是個類對象)。

其實現原理是封裝庫時使用了條件編譯,也就是說他的底層還是分別調用了不同平臺的線程API。

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

1.1. 使用時的注意點

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

當創(chuàng)建一個線程對象后,沒有提供線程函數,該對象實際沒有對應任何線程。

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

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

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

當創(chuàng)建一個線程對象后,并且給線程關聯線程函數,該線程就被啟動,與主線程一起運行。線程函數一般情況下可按照以下三種方式提供:函數指針、lambda表達式、函數對象

#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()
 // 線程函數為函數指針
 thread t1(ThreadFunc, 10);
 
 // 線程函數為lambda表達式
 thread t2([]{cout << "Thread2" << endl; });
 // 線程函數為函數對象
 TF tf;
 thread t3(tf);
 t1.join();
 t2.join();
 t3.join();
 cout << "Main thread!" << endl;
 return 0; }

4.thread類是防拷貝的,不允許拷貝構造以及賦值,但是可以移動構造和移動賦值,即將一個線程對象關聯線程的狀態(tài)轉移給其他線程對象,轉移期間不意向線程的執(zhí)行

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

  • 采用無參構造函數構造的線程對象(沒有提供線程函數,即這個線程本身就是無效的)
  • 線程對象的狀態(tài)已經轉移給其他線程對象(資源被轉移)
  • 線程已經調用jion或者detach結束(線程已結束,或者已經分離)

1.2. 線程函數參數

線程函數的參數是以值拷貝的方式拷貝到線程??臻g中的,因此:即使線程參數為引用類型,在線程中修改后也不能修改外部實參,因為其實際引用的是線程棧中的拷貝,而不是外部實參。

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

1.3. join與detach

啟動了一個線程后,當這個線程結束的時候,如何去回收線程所使用的資源呢?thread庫給我們兩種選擇:

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

#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():該函數被調用后,新線程與線程對象分離,不再被線程對象所表達,就不能通過線程對象控制線程了,新線程會在后臺運行,其所有權和控制權將會交給c++運行庫。同時,C++運行庫保證,當線程退出時,其相關資源的能夠正確的回收。

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

二、原子性操作庫

多線程最主要的問題是共享數據帶來的問題(即線程安全)。如果共享數據都是只讀的,那么沒問題,因為只讀操作不會影響到數據,更不會涉及對數據的修改,所以所有線程都會獲得同樣的數據。但是,當一個或多個線程要修改共享數據時,就會產生很多潛在的麻煩。

2.1. atomic

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

注意:原子類型通常屬于"資源型"數據,多個線程只能訪問單個原子類型的拷貝,因此在C++11中,原子類型只能從其模板參數中進行構造,不允許原子類型進行拷貝構造、移動構造以及operator=等,為了防止意外,標準庫已經將atmoic模板類中的拷貝構造、移動構造、賦值運算符重載默認刪除掉了

#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)境下,如果想要保證某個變量的安全性,只要將其設置成對應的原子類型即可,即高效又不容易出現死鎖問題。但是有些情況下,我們可能需要保證一段代碼的安全性,那么就只能通過鎖的方式來進行控制。

1.lock、unlock:

線程函數調用lock()時,可能會發(fā)生以下三種情況:

  • 如果該互斥量當前沒有被鎖住,則調用線程將該互斥量鎖住,直到調用 unlock之前,該線程一直擁有該鎖。
  • 如果當前互斥量被其他線程鎖住,則當前的調用線程被阻塞住。
  • 如果當前互斥量被當前調用線程鎖住,則會產生死鎖(同一線程,不可以連續(xù)調用兩次鎖)。

線程函數調用try_lock()時,可能會發(fā)生以下三種情況:

  • 如果當前互斥量沒有被其他線程占有,則該線程鎖住互斥量,直到該線程調用 unlock 釋放互斥量。
  • 如果當前互斥量被其他線程鎖住,則當前調用線程返回 false,而并不會被阻塞掉(非阻塞式)。
  • 如果當前互斥量被當前調用線程鎖住,則會產生死鎖。

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

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

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

try_lock_until()
接受一個時間點作為參數,在指定時間點未到來之前線程如果沒有獲得鎖則被阻塞住,如果在此期間其他線程釋放了鎖,則該線程可以獲得對互斥量的鎖,如果超時(即在指定時間內還是沒有獲得鎖),則返回 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);//接收一個時間范圍
		Lock.try_lock_until(chrono::steady_clock::now() + timeout);//接收一個時間點
		sum++;
		Lock.unlock();
	}
}

4.recursive_timed_mutex
是recursive_mutex、timed_mutex兩種鎖的結合。

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

template<class _Mutex>
class lock_guard
{
public:

	// 構造函數加鎖
	explicit lock_guard(_Mutex& _Mtx)
		: _MyMutex(_Mtx)
	{
		_MyMutex.lock();
	}
	//通過析構函數釋放鎖
	~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的方式,對其管理的互斥量進行了封裝,在需要
加鎖的地方,只需要用上述介紹的任意互斥體實例化一個lock_guard,調用構造函數成功上鎖,出作用域前,lock_guard對象要被銷毀,調用析構函數自動解鎖,可以有效避免死鎖問題。
lock_guard的缺陷:太單一,用戶沒有辦法對該鎖進行控制,因此C++11又提供了unique_lock。

6.unique_lock

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

  • 上鎖/解鎖操作:lock、try_lock、try_lock_for、try_lock_until和unlock
  • 修改操作:移動賦值、交換(swap:與另一個unique_lock對象互換所管理的互斥量所有權)、釋放(release:返回它所管理的互斥量對象的指針,并釋放所有權)
  • 獲取屬性:owns_lock(返回當前對象是否上了鎖)、operator bool()(與owns_lock()的功能相同)、mutex(返回當前unique_lock所管理的互斥量的指針)。

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

#include<iostream>
#include<thread>
#include<atomic>
#include<vector>
using namespace std;
int main()
{

	atomic<int>x = 0;
	//m個線程對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;
}

四、條件變量

需要注意的是,傳入wait的鎖不能是lock_guard,這是因為lock_guard沒有解鎖接口

喚醒:

Notify one -> 喚醒一個
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;
	//使用兩個線程打印0-n的數,一個打印奇數,一個打印偶數
	thread t1([&]() 
		{
			for (int i = 0; i < n; i+=2)
			{
			
				cout << this_thread::get_id() << ":" << i << endl;
				cv2.notify_one();//打印偶數以后通知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打印奇數以后,通知t1
			}
		});
	t1.join();
	t2.join();
	return 0;
}

到此這篇關于詳解C++11中的線程庫的文章就介紹到這了,更多相關C++11線程庫內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • OpenCV?通過Mat遍歷圖像的方法匯總

    OpenCV?通過Mat遍歷圖像的方法匯總

    對圖像中的所有點或特殊點進行運算,所以遍歷圖像就顯得很重要,如何高效的遍歷圖像是一個很值得探討的問題,本文給大家?guī)砹硕喾N方法操作OpenCV?通過Mat遍歷圖像,感興趣的朋友一起看看吧
    2022-02-02
  • c++模板自定義數組

    c++模板自定義數組

    這篇文章主要介紹了c++模板自定義數組,通過制造通用模板,創(chuàng)建自定義的數組展開文章相關內容,具有一的參考價值,需要的小伙伴可以參考一下
    2022-03-03
  • C++中#pragma once與#ifndef對比分析

    C++中#pragma once與#ifndef對比分析

    當我們編寫C++代碼時,經常需要使用頭文件來引入一些常用的函數、類或者變量,如果一個頭文件被重復包含,就會導致編譯錯誤或者運行時錯,為了避免發(fā)生,我們需要使用預處理指令來防止頭文件被重復包含,常用的預處理指令有#pragma once和#ifndef,需要的朋友可以參考下
    2023-05-05
  • C++ stringstream類用法詳解

    C++ stringstream類用法詳解

    這篇文章主要介紹了C++ stringstream類用法詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下
    2021-08-08
  • C++繼承的定義與注意事項

    C++繼承的定義與注意事項

    這篇文章主要給大家介紹了關于C++繼承的定義與注意事項的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-05-05
  • 詳解C++中的菱形繼承問題

    詳解C++中的菱形繼承問題

    這篇文章主要介紹了詳解C++中的菱形繼承問題,在多繼承結構中,存在著很多問題,比如從不同基類中繼承了同名成員,派生類中也定義了同名成員,這種二義性問題很好解決,加上要訪問的基類的類名限制就可以了,需要的朋友可以參考下
    2023-08-08
  • 詳解在C++中顯式默認設置的函數和已刪除的函數的方法

    詳解在C++中顯式默認設置的函數和已刪除的函數的方法

    這篇文章主要介紹了在C++中顯式默認設置的函數和已刪除的函數的方法,文中講到了C++11標準中的新特性,需要的朋友可以參考下
    2016-01-01
  • C++智能指針讀書筆記

    C++智能指針讀書筆記

    本篇隨筆僅作為個人學習《C++ Primer》智能指針一節(jié)后的部分小結,抄書嚴重,伴隨個人理解。主要介紹shared_ptr、make_shared、weak_ptr的用法和聯系
    2015-11-11
  • C++封裝遠程注入類CreateRemoteThreadEx實例

    C++封裝遠程注入類CreateRemoteThreadEx實例

    這篇文章主要介紹了C++封裝遠程注入類CreateRemoteThreadEx實例,詳細講述了注入DLL到指定的地址空間以及從指定的地址空間卸載DLL的方法,需要的朋友可以參考下
    2014-10-10
  • C語言經典例程100例(經典c程序100例)

    C語言經典例程100例(經典c程序100例)

    這篇文章主要介紹了C語言經典例程100例,經典c程序100例,學習c語言的朋友可以參考一下
    2018-03-03

最新評論