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

深入解析C++11?lambda表達式/包裝器/線程庫

 更新時間:2022年05月19日 09:47:13   作者:可口也可樂  
這篇文章主要介紹了C++11?lambda表達式/包裝器/線程庫的相關(guān)知識,本文通過示例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下

零、前言

本章是講解學(xué)習(xí)C++11語法新特性的第三篇文章,主要學(xué)習(xí)lambda表達式,包裝器,線程庫

一、lambda表達式

1、lambda的引入

在C++98中,如果想要對一個數(shù)據(jù)集合中的元素進行排序,可以使用std::sort方法

示例:

#include <algorithm>
#include <functional>
int main()
{
	int array[] = { 4,1,8,5,3,7,0,9,2,6 };
	// 默認按照小于比較,排出來結(jié)果是升序
	std::sort(array, array + sizeof(array) / sizeof(array[0]));
	for (int i = 0; i < 10; i++)
	{
		cout << array[i] << " ";
	}cout << endl;
	// 如果需要降序,需要改變元素的比較規(guī)則
	std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
	for (int i = 0; i < 10; i++)
	{
		cout << array[i] << " ";
	}cout << endl;
	return 0;
}

效果:

注:如果待排序元素為自定義類型,需要用戶定義排序時的比較規(guī)則

示例:

struct Goods
{
	string _name;
	double _price;
};
struct Compare
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price <= gr._price;
	}
};
int main()
{
	Goods gds[] = { { "蘋果", 2.1 }, { "香蕉", 3 }, { "橙子", 2.2 }, {"菠蘿", 1.5} };
	sort(gds, gds + sizeof(gds) / sizeof(gds[0]), Compare());
	for (int i = 0; i < 4; i++)
		cout << gds[i]._name << ":"<<gds[i]._price<<" ";
	cout << endl;
	return 0;
}

效果:

概念及引入:

隨著C++語法的發(fā)展,人們開始覺得上面的寫法太復(fù)雜了,每次為了實現(xiàn)一個algorithm算法, 都要重新去寫一個類,如果每次比較的邏輯不一樣,還要去實現(xiàn)多個類,特別是相同類的命名,這些都給編程者帶來了極大的不便。因此,在C11語法中出現(xiàn)了Lambda表達式

示例:

int main()
{
	Goods gds[] = { { "蘋果", 2.1 }, { "香蕉", 3 }, { "橙子", 2.2 }, {"菠蘿", 1.5} };
	sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& l, const Goods& r)
		->bool
		{
			return l._price < r._price;
		});
	return 0;
}

注:可以看出lamb表達式實際是一個匿名函數(shù)

2、lambda表達式語法

lambda表達式書寫格式:

[capture-list] (parameters) mutable -> return-type { statement }

lambda表達式各部分說明:

[capture-list] :

捕捉列表,該列表總是出現(xiàn)在lambda函數(shù)的開始位置,編譯器根據(jù)[]來判斷接下來的代碼是否為lambda函數(shù),捕捉列表能夠捕捉上下文中的變量供lambda函數(shù)使用

(parameters):

參數(shù)列表,與普通函數(shù)的參數(shù)列表一致,如果不需要參數(shù)傳遞,則可以連同()一起省略

mutable:

默認情況下,lambda函數(shù)總是一個const函數(shù), mutable的作用就是讓傳值捕捉的對象可以修改,但是你修改的是傳值拷貝的對象,不影響外面對象,使用該修飾符時,參數(shù)列表不可省略(即使參數(shù)為空)

注:實際中mutable意義不大,除非你就是想傳值捕捉過來,lambda中修改,不影響外面的值

->returntype:

返回值類型,用追蹤返回類型形式聲明函數(shù)的返回值類型,沒有返回值時此部分可省略;返回值類型明確情況下,也可省略,由編譯器對返回類型進行推導(dǎo)

{statement}:

函數(shù)體,在該函數(shù)體內(nèi),除了可以使用其參數(shù)外,還可以使用所有捕獲到的變量

注:在lambda函數(shù)定義中,參數(shù)列表和返回值類型都是可選部分,而捕捉列表和函數(shù)體可以為空,即C++11中最簡單的lambda函數(shù)為:[]{}; 該lambda函數(shù)不能做任何事情

示例:

int main()
{
	// 最簡單的lambda表達式, 該lambda表達式?jīng)]有任何意義
	[] {};
	// 省略參數(shù)列表和返回值類型,返回值類型由編譯器推導(dǎo)為int
	int a = 3, b = 4;
	[=]{ return a + 3; };
	// 省略了返回值類型,無返回值類型
	auto fun1 = [&](int c) { b = a + c; };
	fun1(10);
	cout << a << " " << b << endl;
	// 各部分都很完善的lambda函數(shù)
	auto fun2 = [=, &b](int c)->int {return b += a + c; };
	cout << fun2(10) << endl;
	// 復(fù)制捕捉x
	int x = 10;
	auto add_x = [x](int a)mutable { x *= 2; return a + x; };//傳值捕捉修改需要mutable修飾
	auto add_x1 = [&x](int a){ x *= 2; return a + x; };//引用捕捉不用
	cout << add_x(10) << endl;
	cout << x << endl;
	cout << add_x1(10) << endl;
	cout << x << endl;
	return 0;
}

效果:

注:lambda表達式實際上可以理解為無名函數(shù),該函數(shù)無法直接調(diào)用,如果想要直接調(diào)用,可借助auto將其賦值給一個變量

3、捕獲列表說明

概念:

捕捉列表描述了上下文中那些數(shù)據(jù)可以被lambda使用,以及使用的方式傳值還是傳引用

使用方式:

[var]:表示值傳遞方式捕捉變量var
[=]:表示值傳遞方式捕獲所有父作用域中的變量(包括this)
[&var]:表示引用傳遞捕捉變量var
[&]:表示引用傳遞捕捉所有父作用域中的變量(包括this)
[this]:表示值傳遞方式捕捉當(dāng)前的this指針

注意:

父作用域指包含lambda函數(shù)的語句塊

語法上捕捉列表可由多個捕捉項組成,并以逗號分割:比如:[=, &a, &b]:以引用傳遞的方式捕捉變量a和b,值傳遞方式捕捉其他所有變量 [&,a, this]:值傳遞方式捕捉變量a和this,引用方式捕捉其他變量

捕捉列表不允許變量重復(fù)傳遞,否則就會導(dǎo)致編譯錯誤:比如:[=, a]:=已經(jīng)以值傳遞方式捕捉了所有變量,捕捉a重復(fù)

在塊作用域以外的lambda函數(shù)捕捉列表必須為空;在塊作用域中的lambda函數(shù)僅能捕捉父作用域中局部變量

lambda表達式之間不能相互賦值,即使看起來類型相同

示例:

void (*PF)();
int main()
{
    auto f1 = []{cout << "hello world" << endl; };
    auto f2 = []{cout << "hello world" << endl; };
    //f1 = f2; // 編譯失敗--->提示找不到operator=()
    // 允許使用一個lambda表達式拷貝構(gòu)造一個新的副本
    auto f3(f2);
    f3();
    // 可以將沒有捕獲任何變量的lambda表達式賦值給相同類型的函數(shù)指針
    PF = f2;
    PF();
    return 0;
}

解釋:

Lambda是實現(xiàn)了函數(shù)調(diào)用運算符的匿名類(anonymous class)。對于每一個Lambda,編譯器創(chuàng)建匿名類,并定義相應(yīng)的數(shù)據(jù)成員存儲Lambda捕獲的變量。沒有捕獲變量的Lambda不包含任何含成員變量。一個沒有任何成員變量(包括沒有虛函數(shù)表指針)的類型,在空指針上調(diào)用成員函數(shù)也不會有任何的問題,因為它的成員函數(shù)不會通過this指針訪問內(nèi)存。當(dāng)Lambda向函數(shù)指針的轉(zhuǎn)換時,編譯器為Lambda的匿名類實現(xiàn)函數(shù)指針類型轉(zhuǎn)換運算符

4、函數(shù)對象與lambda表達式

函數(shù)對象,又稱為仿函數(shù),即可以想函數(shù)一樣使用的對象,就是在類中重載了operator()運算符的類對象

示例:

class Rate
{
public:
    Rate(double rate): _rate(rate)
    {}
    double operator()(double money, int year)
    { 
        return money * _rate * year;
    }
private:
    double _rate;
};
int main()
{
    // 函數(shù)對象
    double rate = 0.49;
    Rate r1(rate);
    r1(10000, 2);
    // lamber
    auto r2 = [=](double monty, int year)->double{return monty*rate*year; };
    r2(10000, 2);
    return 0;
}

說明:

從使用方式上來看,函數(shù)對象與lambda表達式完全一樣:函數(shù)對象將rate作為其成員變量,在定義對象時給出初始值即可,lambda表達式通過捕獲列表可以直接將該變量捕獲到

示圖:

注:實際在底層編譯器對于lambda表達式的處理方式,完全就是按照函數(shù)對象的方式處理的

二、包裝器

1、function包裝器

概念:

function包裝器也叫作適配器,C++中的function本質(zhì)是一個類模板,也是一個包裝器

由于C++的歷史遺留問題,導(dǎo)致如果想實現(xiàn)一個函數(shù)功能,可以采用函數(shù)名、函數(shù)指針、仿函數(shù)、有名稱的lambda表達式,所有這些都是可調(diào)用的類型

存在的問題:

函數(shù)指針類型太復(fù)雜,不方便使用和理解仿函數(shù)類型是一個類名,沒有指定調(diào)用參數(shù)和返回值,得去看operator()的實現(xiàn)才能看出來lambda表達式在語法層,看不到類型,只能在底層看到其類型,基本都是lambda_uuid

示例:

template<class F, class T>
T useF(F f, T x)
{
	static int count = 0;
	cout << "count:" << ++count << endl;
	cout << "count:" << &count << endl;
	return f(x);
}
double f(double i)
{
	return i / 2;
}
struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};
int main()
{
	// 函數(shù)名
	cout << useF(f, 11.11) << endl;
	// 函數(shù)對象
	cout << useF(Functor(), 11.11) << endl;
	// lamber表達式
	cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
	return 0;
}

效果:

image-20220512181306891

注:對于函數(shù)名稱,仿函數(shù)對象,lambda表達式對象這些都是可調(diào)用的類型,我們發(fā)現(xiàn)發(fā)現(xiàn)useF函數(shù)模板實例化了三份,所以如此豐富的類型,可能會導(dǎo)致模板的效率低下,包裝器可以很好的解決該問題

包裝器原型:

// 類模板原型如下
template <class T> function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>; 

模板參數(shù)說明:

Ret: 被調(diào)用函數(shù)的返回類型

Args…:被調(diào)用函數(shù)的形參

注:std::function在頭文件< functional >

示例:

#include <functional>
int f(int a, int b)
{
	return a + b;
}
struct Functor
{
	int operator() (int a, int b)
	{
		return a + b;
	}
};
class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return a + b;
	}
};
int main()
{
	// 函數(shù)名(函數(shù)指針)
	std::function<int(int, int)> func1 = f;
	cout << func1(1, 2) << endl;
	// 函數(shù)對象
	std::function<int(int, int)> func2 = Functor();
	cout << func2(1, 2) << endl;
	// lamber表達式
	std::function<int(int, int)> func3 = [](const int a, const int b){
		return a + b; 
	};
	cout << func3(1, 2) << endl;
	// 類的成員函數(shù)
	std::function<int(int, int)> func4 = Plus::plusi;
	cout << func4(1, 2) << endl;
	std::function<double(Plus, double, double)> func5 = &Plus::plusd;//對于普通成員的包裝一定要加上&,需要通過指針進行調(diào)用成員函數(shù)
	cout << func5(Plus(), 1.1, 2.2) << endl;//傳入類對象,通過對象進行調(diào)用
	return 0;
}

效果:

包裝器解決模板實例化多份的問題:

#include <functional>
template<class F, class T>
T useF(F f, T x)
{
	static int count = 0;
	cout << "count:" << ++count << endl;
	cout << "count:" << &count << endl;
	return f(x);
}
double f(double i)
{
	return i / 2;
}
struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};
int main()
{
	//將多個可調(diào)用類型進行封裝成相同類型,便于統(tǒng)一調(diào)用
	// 函數(shù)名
	std::function<double(double)> func1 = f;
	cout << useF(func1, 11.11) << endl;
	// 函數(shù)對象
	std::function<double(double)> func2 = Functor();
	cout << useF(func2, 11.11) << endl;
	// lamber表達式
	std::function<double(double)> func3 = [](double d)->double { 
		return d /4; 
	};
	cout << useF(func3, 11.11) << endl;
	return 0;
}

效果:

2、bind 概念:

std::bind函數(shù)定義在頭文件中,是一個函數(shù)模板,它就像一個函數(shù)包裝器(適配器),接受一個可調(diào)用對象(callable object),生成一個新的可調(diào)用對象來“適應(yīng)”原對象的參數(shù)列表一般而言,我們用它可以把一個原本接收N個參數(shù)的函數(shù)fn,通過綁定一些參數(shù),返回一個接收M個(M可以大于N,但這么做沒什么意義)參數(shù)的新函數(shù);同時,使用std::bind函數(shù)還可以實現(xiàn)參數(shù)順序調(diào)整等操作

示例:

#include <functional>
int Plus(int a, int b)
{
	return a + b;
}
class Sub
{
public:
	int sub(int a, int b)
	{
		return a - b;
	}
	static int sub1(int a, int b)
	{
		return a - b;
	}
};
int main()
{
	//普通函數(shù)的綁定
	//表示綁定函數(shù)plus 參數(shù)分別由調(diào)用 func1 的第一,二個參數(shù)指定(placeholders用來表示參數(shù)位占位)
	std::function<int(int, int)> func1 = std::bind(Plus, placeholders::_1,placeholders::_2);
	//auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);直接使用auto識別類型

	//表示綁定函數(shù) plus 的第一,二參數(shù)為: 1, 2
	auto func2 = std::bind(Plus, 1, 2);
	cout << func1(1, 2) << endl;
	cout << func2(2,3) << endl;//func2();也可以不用傳參數(shù)-因為參數(shù)已經(jīng)綁定好了,傳入的參數(shù)沒有實際的作用

	//類函數(shù)的綁定
	//類的成員函數(shù)必須通過類的對象或者指針調(diào)用,因此在bind時,bind的第一個參數(shù)的位置來指定一個類的實列、指針或引用。
	Sub s;
	// 綁定成員函數(shù)
	std::function<int(int, int)> func3 = std::bind(&Sub::sub, s,placeholders::_1, placeholders::_2);
	// 參數(shù)調(diào)換順序
	std::function<int(int, int)> func4 = std::bind(&Sub::sub, s,placeholders::_2, placeholders::_1);
	std::function<int(int, int)> func5 = std::bind(Sub::sub1,placeholders::_2, placeholders::_1);//靜態(tài)成員函數(shù)的綁定-不需要類的示例指針或引用
	cout << func3(1, 2) << endl;
	cout << func4(1, 2) << endl;
	cout << func5(1, 2) << endl;
	return 0;
}

效果:

總結(jié):

bind是對包裝的可調(diào)用類型的進一步封裝,可以根據(jù)自己的需要進行調(diào)整參數(shù)的數(shù)據(jù)及位置,綁定類對象能有優(yōu)化成員函數(shù)的包裝使用,更加符合使用習(xí)慣

三、線程庫

1、線程的概念及使用

thread類的簡單介紹:

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

注:要使用標準庫中的線程,必須包含< thread >頭文件

線程常用接口:

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

注意:

線程是操作系統(tǒng)中的一個概念,是進程中的一個執(zhí)行分支,線程對象可以關(guān)聯(lián)一個線程,用來控制線程以及獲取線程的狀態(tài)

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

示例:

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

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

對應(yīng)結(jié)構(gòu)體的定義:

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

當(dāng)創(chuàng)建一個線程對象后,并且給線程關(guān)聯(lián)線程函數(shù),該線程就被啟動,與主線程一起運行

線程函數(shù)一般情況下可按照以下三種方式提供:

  1. 函數(shù)指針
  2. lambda表達式
  3. 函數(shù)對象

示例:

#include <iostream>
#include <thread>
using namespace std;
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表達式
	thread t2([]() {
		cout << "Thread2" << endl; 
		});
	// 線程函數(shù)為函數(shù)對象
	TF tf;
	thread t3(tf);
	t1.join();
	t2.join();
	t3.join();
	cout << "Main thread!" << endl;
	return 0;
}

效果:

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

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

無效的線程: 采用無參構(gòu)造函數(shù)構(gòu)造的線程對象
線程對象的狀態(tài)已經(jīng)轉(zhuǎn)移給其他線程對象
線程已經(jīng)調(diào)用jion或者detach結(jié)束

面試題:并發(fā)與并行的區(qū)別

并發(fā)指的是多個事情,在同一時間段內(nèi)同時發(fā)生了;并行指的是多個事情,在同一時間點上同時發(fā)生了
并發(fā)的多個任務(wù)之間是互相搶占資源的;并行的多個任務(wù)之間是不互相搶占資源的,只有在多CPU的情況中才會發(fā)生并行

2、線程函數(shù)參數(shù)

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

示例:

#include <iostream>
#include <thread>
using namespace std;
void Func1(int& x)
{
	x += 10;
	return;
}
void Func2(int* x)
{
	*x += 10;
	return;
}
int main()
{
	int a = 10;
	// 在線程函數(shù)中對a修改,不會影響外部實參,因為:線程函數(shù)參數(shù)雖然是引用方式,但其實際引用的是線程棧中的拷貝
	// vs2019會報錯-對于引用的參數(shù)這么傳入
	//thread t1(Func1, a);
	//t1.join();
	//cout << a << endl;
	// 如果想要通過形參改變外部實參時,必須借助std::ref()函數(shù)
	thread t2(Func1, ref(a));
	t2.join();
	cout << a << endl;
	// 地址的拷貝
	thread t3(Func2, &a);
	t3.join();
	cout << a << endl;
	return 0;
}

效果:

注意:

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

示例:

#include <iostream>
#include <thread>
using namespace std;
class A
{
public:
	void Func1(int x)
	{
		cout << x << endl;
	}
	static void Func2(int x)
	{
		cout << x << endl;
	}
};
int main()
{
	A a;
	//普通成員函數(shù)需要傳入類的實例或者指針
	thread t1(&A::Func1, a, 10);
	t1.join();
	//靜態(tài)成員函數(shù)則不用
	thread t2(&A::Func2, 10);
	t2.join();
	return 0;
}

效果:

3、原子性操作庫(atomic)

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

示例:

#include <iostream>
#include <thread>
using namespace std;

unsigned long sum = 0L;
void fun(size_t num)
{
	for (size_t i = 0; i < num; ++i)
		sum++;
}
int main()
{
	cout << "Before joining,sum = " << sum << std::endl;
	thread t1(fun, 10000000);
	thread t2(fun, 10000000);
	t1.join();
	t2.join();
	cout << "After joining,sum = " << sum << std::endl;
	return 0;
}

效果:

C++98中傳統(tǒng)的解決方式:可以對共享修改的數(shù)據(jù)可以加鎖保護

示例:

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

std::mutex m;
unsigned long sum = 0L;
void fun(size_t num)
{
	for (size_t i = 0; i < num; ++i)
	{
		m.lock();
		sum++;
		m.unlock();
	}
}
int main()
{
	cout << "Before joining,sum = " << sum << std::endl;
	thread t1(fun, 10000000);
	thread t2(fun, 10000000);
	t1.join();
	t2.join();
	cout << "After joining,sum = " << sum << std::endl;
	return 0;
}

效果:

加鎖缺陷:

只要一個線程在sum++時,其他線程就會被阻塞,會影響程序運行的效率,而且鎖如果控制不好,還容易造成死鎖

因此C++11中引入了原子操作,所謂原子操作:即不可被中斷的一個或一系列操作C++11引入的原子操作類型,使得線程間數(shù)據(jù)的同步變得非常高效

示圖:原子操作類型

image-20220513113538668

注:需要使用以上原子操作變量時,必須添加頭文件#include < atomic >

示例:

#include <iostream>
#include <thread>
using namespace std;
#include <atomic>

atomic_long sum{ 0 };
void fun(size_t num)
{
	for (size_t 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;
}

效果:

注意:

int main()
{
	thread t1(fun, 1000000);
	thread t2(fun, 1000000);
	t1.join();
	t2.join();
	//printf("%d\n", sum);vs2019存在類型不匹配問題
	//解決方式
	//1.
	printf("%ld\n", sum.load());
	//2.
	cout << sum << endl;
	//3.
	printf("%ld\n", (long)sum);
	return 0;
}

atomic類模板:

在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)造、賦值運算符重載默認刪除掉了

示例:

#include <atomic>
int main()
{
    atomic<int> a1(0);
    //atomic<int> a2(a1); // 編譯失敗
    atomic<int> a2(0);
    //a2 = a1; // 編譯失敗
    return 0;
}

4、lock_guard與unique_lock

概念及引入:

在多線程環(huán)境下,如果想要保證某個變量的安全性,只要將其設(shè)置成對應(yīng)的原子類型即可,即高效又不容易出現(xiàn)死鎖問題

但是有些情況下,我們可能需要保證一段代碼的安全性,那么就只能通過鎖的方式來進行控制,鎖控制不好時,可能會造成死鎖 ,最常見的比如在鎖中間代碼返回,或者在鎖的范圍內(nèi)拋異常

因此:C++11采用RAII的方式對鎖進行了封裝,即lock_guard和unique_lock

1、mutex的種類

在C++11中,Mutex總共包了四個互斥量的種類: std::mutex

C++11提供的最基本的互斥量,該類的對象之間不能拷貝,也不能進行移動

mutex最常用的三個函數(shù):

函數(shù)名函數(shù)功能
lock()上鎖:鎖住互斥量
unlock()解鎖:釋放對互斥量的所有權(quán)
try_lock()嘗試鎖住互斥量,如果互斥量被其他線程占有,則當(dāng)前線程也不會被阻塞

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

如果該互斥量當(dāng)前沒有被鎖住,則調(diào)用線程將該互斥量鎖住,直到調(diào)用 unlock之前,該線程一直擁有該鎖
如果當(dāng)前互斥量被其他線程鎖住,則當(dāng)前的調(diào)用線程被阻塞住
如果當(dāng)前互斥量被當(dāng)前調(diào)用線程鎖住,則會產(chǎn)生死鎖(deadlock)

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

如果當(dāng)前互斥量沒有被其他線程占有,則該線程鎖住互斥量,直到該線程調(diào)用 unlock 釋放互斥量
如果當(dāng)前互斥量被其他線程鎖住,則當(dāng)前調(diào)用線程返回 false,而并不會被阻塞掉
如果當(dāng)前互斥量被當(dāng)前調(diào)用線程鎖住,則會產(chǎn)生死鎖(deadlock)

std::recursive_mutex

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

std::timed_mutex

比 std::mutex 多了兩個成員函數(shù),try_lock_for(),try_lock_until() ,
try_lock_for()
接受一個時間范圍,表示在這一段時間范圍之內(nèi)線程如果沒有獲得鎖則被阻塞?。ㄅcstd::mutex 的 try_lock() 不同,try_lock 如果被調(diào)用時沒有獲得鎖則直接返回false),如果在此期間其他線程釋放了鎖,則該線程可以獲得對互斥量的鎖,如果超時(即在指定時間內(nèi)還是沒有獲得鎖),則返回 false
try_lock_until()接受一個時間點作為參數(shù),在指定時間點未到來之前線程如果沒有獲得鎖則被阻塞住,如果在此期間其他線程釋放了鎖,則該線程可以獲得對互斥量的鎖,如果超時(即在指定時間內(nèi)還是沒有獲得鎖),則返回 false

std::recursive_timed_mutex

recursive_mutex和timed_mutex的結(jié)合

2、lock_guard

std::lock_gurad 是 C++11 中定義的模板類。

定義如下:

template<class _Mutex>
class lock_guard
{
public:
	// 在構(gòu)造lock_gard時上鎖
	explicit lock_guard(_Mutex& _Mtx)
		: _MyMutex(_Mtx)
	{
		_MyMutex.lock();
	}
	lock_guard(_Mutex & _Mtx, adopt_lock_t)
		: _MyMutex(_Mtx)
	{}
	// 在析構(gòu)lock_gard時解鎖
	~lock_guard() _NOEXCEPT
	{
		_MyMutex.unlock();
	}
	lock_guard(const lock_guard&) = delete;
	lock_guard& operator=(const lock_guard&) = delete;
private:
	_Mutex& _MyMutex;
};

解釋:

lock_guard類模板主要是通過RAII的方式,對其管理的互斥量進行了封裝,在需要加鎖的地方,只需要用上述介紹的任意互斥體實例化一個lock_guard,調(diào)用構(gòu)造函數(shù)成功上鎖,出作用域前,lock_guard對象要被銷毀,調(diào)用析構(gòu)函數(shù)自動解鎖,可以有效避免死鎖問題

lock_guard的缺陷:

太單一,用戶沒有辦法對該鎖進行控制,因此C++11又提供了unique_lock

3、unique_lock

概念及介紹:

與lock_gard類似,unique_lock類模板也是采用RAII的方式對鎖進行了封裝,并且也是以獨占所有權(quán)的方式管理mutex對象的上鎖和解鎖操作,即其對象之間不能發(fā)生拷貝
在構(gòu)造(或移動(move)賦值)時,unique_lock 對象需要傳遞一個 Mutex 對象作為它的參數(shù),新創(chuàng)建的unique_lock 對象負責(zé)傳入的 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(返回當(dāng)前對象是否上了鎖)、operator bool()(與owns_lock()的功能相同)、mutex(返回當(dāng)前unique_lock所管理的互斥量的指針)

5、兩個線程交替打印奇數(shù)偶數(shù)

錯誤示例:使用普通的條件變量

先讓打印偶數(shù)線程獲取到所資源,然后在條件變量下等待并將鎖資源釋放
打印奇數(shù)獲取到鎖進行打印,打印后先喚醒在條件變量下等待的線程,再等待在并釋放鎖資源
再打印偶數(shù)線程被喚醒并競爭到鎖資源,進行打印…
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
int main()
{
	int i = 1;
	int j = 2;
	bool flg = true;
	mutex mtx;
	condition_variable cv;
	//存在時間片切出去的問題
	thread t2([&j, &mtx, &cv]() {
		while (j <= 100)
		{
			std::unique_lock<mutex> lock(mtx);
			cv.wait(lock);
			cout << std::this_thread::get_id() << ":" << j << endl;
			j += 2;
			cv.notify_one();
		}
		});
	thread t1([&i, &mtx, &cv]() {
		while (i <= 100)
		{
			std::unique_lock<mutex> lock(mtx);
			cout << std::this_thread::get_id() << ":" << i << endl;
			i += 2;
			cv.notify_one();
			cv.wait(lock);
		}
		});
	t1.join();
	t2.join();
	return 0;
}

問題示例:

當(dāng)打印偶數(shù)線程獲取鎖后,在要等待在條件變量下之前時,時間片到了線程被切出去,再等到打印奇數(shù)線程執(zhí)行喚醒等待條件變量下的線程時沒有線程被喚醒,當(dāng)打印偶數(shù)線程時間片切回時,依舊會等待在條件變量下,而此時打印奇數(shù)線程也等待在條件變量下,此時沒人進行喚醒兩線程也就會一直進行等待

效果:

正確示例:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
int main()
{
	int i = 1;
	int j = 2;
	bool flg = true;
	mutex mtx;
	condition_variable cv;
	//正確寫法
	thread t1([&i, &mtx, &cv,&flg]() {
		while (i <= 100)
		{
			std::unique_lock<mutex> lock(mtx);
			cv.wait(lock, [&flg]() { return flg; });//根據(jù)條件判斷是否需要進行阻塞等待
			cout << std::this_thread::get_id() << ":" << i << endl;
			i+=2;
			flg = false;//更改條件變量-使得另一個線程執(zhí)行,該線程會等待住
			cv.notify_one();//進行喚醒等待條件變量下的線程
		}
	});
	thread t2([&j, &mtx, &cv, &flg]() {
		while (j <= 100)
		{
			std::unique_lock<mutex> lock(mtx);
			cv.wait(lock, [&flg]() { return !flg; });
			cout << std::this_thread::get_id() << ":" << j << endl;
			j += 2;
			flg = true;
			cv.notify_one();
		}
		});
	t1.join();
	t2.join();
	return 0;
}

效果:

確示例:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
int main()
{
	int i = 1;
	int j = 2;
	bool flg = true;
	mutex mtx;
	condition_variable cv;
	//正確寫法
	thread t1([&i, &mtx, &cv,&flg]() {
		while (i <= 100)
		{
			std::unique_lock<mutex> lock(mtx);
			cv.wait(lock, [&flg]() { return flg; });//根據(jù)條件判斷是否需要進行阻塞等待
			cout << std::this_thread::get_id() << ":" << i << endl;
			i+=2;
			flg = false;//更改條件變量-使得另一個線程執(zhí)行,該線程會等待住
			cv.notify_one();//進行喚醒等待條件變量下的線程
		}
	});
	thread t2([&j, &mtx, &cv, &flg]() {
		while (j <= 100)
		{
			std::unique_lock<mutex> lock(mtx);
			cv.wait(lock, [&flg]() { return !flg; });
			cout << std::this_thread::get_id() << ":" << j << endl;
			j += 2;
			flg = true;
			cv.notify_one();
		}
		});
	t1.join();
	t2.join();
	return 0;
}

效果:

到此這篇關(guān)于C++11 lambda表達式/包裝器/線程庫的文章就介紹到這了,更多相關(guān)C++11 lambda表達式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C/C++中extern函數(shù)使用詳解

    C/C++中extern函數(shù)使用詳解

    extern可以置于變量或者函數(shù)前,以標示變量或者函數(shù)的定義在別的文件中,提示編譯器遇到此變量和函數(shù)時在其他模塊中尋找其定義。此外extern也可用來進行鏈接指定
    2022-09-09
  • C++求所有頂點之間的最短路徑(用Dijkstra算法)

    C++求所有頂點之間的最短路徑(用Dijkstra算法)

    這篇文章主要為大家詳細介紹了C++用Dijkstra算法求所有頂點之間的最短路徑,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-04-04
  • C++命名空間域的實現(xiàn)示例

    C++命名空間域的實現(xiàn)示例

    命名空間域就是一個獨立的空間外面不能直接調(diào)用該空間域只能用訪問限定符指定訪問該空間域,本文主要介紹了C++命名空間域的實現(xiàn)示例,具有一定的參考價值,感興趣的可以了解一下
    2024-01-01
  • 淺析c++中new和delete的用法

    淺析c++中new和delete的用法

    以下是對c++中new和delete的用法進行了詳細的分析介紹,需要的朋友可以過來參考下
    2013-09-09
  • C語言中bool和float的用法實例解析

    C語言中bool和float的用法實例解析

    這篇文章主要介紹了C語言中bool類型和float類型的相關(guān)資料,bool類型用于聲明布爾變量,只有true和false兩種值,float類型用于存儲單精度浮點數(shù),文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-11-11
  • 利用Qt自帶的媒體模塊實現(xiàn)播放mp4文件

    利用Qt自帶的媒體模塊實現(xiàn)播放mp4文件

    這篇文章主要為大家詳細介紹了如何使用Qt自帶的媒體模塊,播放mp4等媒體文件功能,文中的示例代碼講解詳細,有需要的小伙伴可以參考一下
    2024-04-04
  • c語言同名標靶點自動匹配算法實現(xiàn)實例代碼

    c語言同名標靶點自動匹配算法實現(xiàn)實例代碼

    這篇文章主要介紹了c語言同名標靶點自動匹配算法實現(xiàn)實例代碼,分享了相關(guān)代碼示例,小編覺得還是挺不錯的,具有一定借鑒價值,需要的朋友可以參考下
    2018-02-02
  • 使用Qt開發(fā)實現(xiàn)字幕滾動效果

    使用Qt開發(fā)實現(xiàn)字幕滾動效果

    我們經(jīng)常能夠在外面看到那種滾動字幕,那么就拿qt來做一個吧,文章通過代碼示例給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作有有一定的參考價值,需要的朋友可以參考下
    2023-11-11
  • ipv6實現(xiàn)udp編程示例

    ipv6實現(xiàn)udp編程示例

    這篇文章主要介紹了ipv6實現(xiàn)udp編程示例,需要的朋友可以參考下
    2014-03-03
  • C++實現(xiàn)十大排序算法及排序算法常見問題

    C++實現(xiàn)十大排序算法及排序算法常見問題

    法是程序的靈魂,無論學(xué)習(xí)什么語言,做什么工程項目,都要考慮算法的效率實現(xiàn),下面這篇文章主要給大家介紹了關(guān)于C++實現(xiàn)十大排序算法及排序算法常見問題的相關(guān)資料,需要的朋友可以參考下
    2021-09-09

最新評論