亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

零、前言

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

一、lambda表達(dá)式

1、lambda的引入

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

示例:

#include <algorithm>
#include <functional>
int main()
{
	int array[] = { 4,1,8,5,3,7,0,9,2,6 };
	// 默認(rèn)按照小于比較,排出來結(jié)果是升序
	std::sort(array, array + sizeof(array) / sizeof(array[0]));
	for (int i = 0; i < 10; i++)
	{
		cout << array[i] << " ";
	}cout << endl;
	// 如果需要降序,需要改變?cè)氐谋容^規(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;
}

效果:

注:如果待排序元素為自定義類型,需要用戶定義排序時(shí)的比較規(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ù)雜了,每次為了實(shí)現(xiàn)一個(gè)algorithm算法, 都要重新去寫一個(gè)類,如果每次比較的邏輯不一樣,還要去實(shí)現(xiàn)多個(gè)類,特別是相同類的命名,這些都給編程者帶來了極大的不便。因此,在C11語法中出現(xiàn)了Lambda表達(dá)式

示例:

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表達(dá)式實(shí)際是一個(gè)匿名函數(shù)

2、lambda表達(dá)式語法

lambda表達(dá)式書寫格式:

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

lambda表達(dá)式各部分說明:

[capture-list] :

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

(parameters):

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

mutable:

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

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

->returntype:

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

{statement}:

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

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

示例:

int main()
{
	// 最簡(jiǎn)單的lambda表達(dá)式, 該lambda表達(dá)式?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表達(dá)式實(shí)際上可以理解為無名函數(shù),該函數(shù)無法直接調(diào)用,如果想要直接調(diào)用,可借助auto將其賦值給一個(gè)變量

3、捕獲列表說明

概念:

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

使用方式:

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

注意:

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

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

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

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

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

示例:

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

解釋:

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

4、函數(shù)對(duì)象與lambda表達(dá)式

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

示例:

class Rate
{
public:
    Rate(double rate): _rate(rate)
    {}
    double operator()(double money, int year)
    { 
        return money * _rate * year;
    }
private:
    double _rate;
};
int main()
{
    // 函數(shù)對(duì)象
    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ù)對(duì)象與lambda表達(dá)式完全一樣:函數(shù)對(duì)象將rate作為其成員變量,在定義對(duì)象時(shí)給出初始值即可,lambda表達(dá)式通過捕獲列表可以直接將該變量捕獲到

示圖:

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

二、包裝器

1、function包裝器

概念:

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

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

存在的問題:

函數(shù)指針類型太復(fù)雜,不方便使用和理解仿函數(shù)類型是一個(gè)類名,沒有指定調(diào)用參數(shù)和返回值,得去看operator()的實(shí)現(xiàn)才能看出來lambda表達(dá)式在語法層,看不到類型,只能在底層看到其類型,基本都是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ù)對(duì)象
	cout << useF(Functor(), 11.11) << endl;
	// lamber表達(dá)式
	cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
	return 0;
}

效果:

image-20220512181306891

注:對(duì)于函數(shù)名稱,仿函數(shù)對(duì)象,lambda表達(dá)式對(duì)象這些都是可調(diào)用的類型,我們發(fā)現(xiàn)發(fā)現(xiàn)useF函數(shù)模板實(shí)例化了三份,所以如此豐富的類型,可能會(huì)導(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ù)對(duì)象
	std::function<int(int, int)> func2 = Functor();
	cout << func2(1, 2) << endl;
	// lamber表達(dá)式
	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;//對(duì)于普通成員的包裝一定要加上&,需要通過指針進(jìn)行調(diào)用成員函數(shù)
	cout << func5(Plus(), 1.1, 2.2) << endl;//傳入類對(duì)象,通過對(duì)象進(jìn)行調(diào)用
	return 0;
}

效果:

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

#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()
{
	//將多個(gè)可調(diào)用類型進(jìn)行封裝成相同類型,便于統(tǒng)一調(diào)用
	// 函數(shù)名
	std::function<double(double)> func1 = f;
	cout << useF(func1, 11.11) << endl;
	// 函數(shù)對(duì)象
	std::function<double(double)> func2 = Functor();
	cout << useF(func2, 11.11) << endl;
	// lamber表達(dá)式
	std::function<double(double)> func3 = [](double d)->double { 
		return d /4; 
	};
	cout << useF(func3, 11.11) << endl;
	return 0;
}

效果:

2、bind 概念:

std::bind函數(shù)定義在頭文件中,是一個(gè)函數(shù)模板,它就像一個(gè)函數(shù)包裝器(適配器),接受一個(gè)可調(diào)用對(duì)象(callable object),生成一個(gè)新的可調(diào)用對(duì)象來“適應(yīng)”原對(duì)象的參數(shù)列表一般而言,我們用它可以把一個(gè)原本接收N個(gè)參數(shù)的函數(shù)fn,通過綁定一些參數(shù),返回一個(gè)接收M個(gè)(M可以大于N,但這么做沒什么意義)參數(shù)的新函數(shù);同時(shí),使用std::bind函數(shù)還可以實(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 的第一,二個(gè)參數(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í)別類型

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

	//類函數(shù)的綁定
	//類的成員函數(shù)必須通過類的對(duì)象或者指針調(diào)用,因此在bind時(shí),bind的第一個(gè)參數(shù)的位置來指定一個(gè)類的實(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是對(duì)包裝的可調(diào)用類型的進(jìn)一步封裝,可以根據(jù)自己的需要進(jìn)行調(diào)整參數(shù)的數(shù)據(jù)及位置,綁定類對(duì)象能有優(yōu)化成員函數(shù)的包裝使用,更加符合使用習(xí)慣

三、線程庫

1、線程的概念及使用

thread類的簡(jiǎn)單介紹:

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

注:要使用標(biāo)準(zhǔn)庫中的線程,必須包含< thread >頭文件

線程常用接口:

函數(shù)名功能
thread()構(gòu)造一個(gè)線程對(duì)象,沒有關(guān)聯(lián)任何線程函數(shù),即沒有啟動(dòng)任何線程
thread(fn, args1, args2, …)構(gòu)造一個(gè)線程對(duì)象,并關(guān)聯(lián)線程函數(shù)fn,args1,args2,…為線程函數(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)

注意:

線程是操作系統(tǒng)中的一個(gè)概念,是進(jìn)程中的一個(gè)執(zhí)行分支,線程對(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;
    return 0;
}

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

對(duì)應(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)建一個(gè)線程對(duì)象后,并且給線程關(guān)聯(lián)線程函數(shù),該線程就被啟動(dòng),與主線程一起運(yùn)行

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

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

示例:

#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表達(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;
}

效果:

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

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

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

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

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

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

線程函數(shù)的參數(shù)是以值拷貝的方式拷貝到線程棧空間中的,因此:即使線程參數(shù)為引用類型,在線程中修改后也不能修改外部實(shí)參,因?yàn)槠鋵?shí)際引用的是線程棧中的拷貝,而不是外部實(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ù)中對(duì)a修改,不會(huì)影響外部實(shí)參,因?yàn)椋壕€程函數(shù)參數(shù)雖然是引用方式,但其實(shí)際引用的是線程棧中的拷貝
	// vs2019會(huì)報(bào)錯(cuò)-對(duì)于引用的參數(shù)這么傳入
	//thread t1(Func1, a);
	//t1.join();
	//cout << a << endl;
	// 如果想要通過形參改變外部實(shí)參時(shí),必須借助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ù)時(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ù)需要傳入類的實(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ù)都是只讀的,那么沒問題,因?yàn)橹蛔x操作不會(huì)影響到數(shù)據(jù),更不會(huì)涉及對(duì)數(shù)據(jù)的修改,所以所有線程都會(huì)獲得同樣的數(shù)據(jù);但是,當(dāng)一個(gè)或多個(gè)線程要修改共享數(shù)據(jù)時(shí),就會(huì)產(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)的解決方式:可以對(duì)共享修改的數(shù)據(jù)可以加鎖保護(hù)

示例:

#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;
}

效果:

加鎖缺陷:

只要一個(gè)線程在sum++時(shí),其他線程就會(huì)被阻塞,會(huì)影響程序運(yùn)行的效率,而且鎖如果控制不好,還容易造成死鎖

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

示圖:原子操作類型

image-20220513113538668

注:需要使用以上原子操作變量時(shí),必須添加頭文件#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中,程序員不需要對(duì)原子類型變量進(jìn)行加鎖解鎖操作,線程能夠?qū)υ宇愋妥兞炕コ獾脑L問,更為普遍的,程序員可以使用atomic類模板,定義出需要的任意原子類型

atmoic<T> t; // 聲明一個(gè)類型為T的原子類型變量t

注意:

原子類型通常屬于"資源型"數(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 <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)境下,如果想要保證某個(gè)變量的安全性,只要將其設(shè)置成對(duì)應(yīng)的原子類型即可,即高效又不容易出現(xiàn)死鎖問題

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

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

1、mutex的種類

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

C++11提供的最基本的互斥量,該類的對(duì)象之間不能拷貝,也不能進(jìn)行移動(dòng)

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

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

線程函數(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)生死鎖(deadlock)

線程函數(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)生死鎖(deadlock)

std::recursive_mutex

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

std::timed_mutex

比 std::mutex 多了兩個(gè)成員函數(shù),try_lock_for(),try_lock_until() ,
try_lock_for()
接受一個(gè)時(shí)間范圍,表示在這一段時(shí)間范圍之內(nèi)線程如果沒有獲得鎖則被阻塞?。ㄅcstd::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

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時(shí)上鎖
	explicit lock_guard(_Mutex& _Mtx)
		: _MyMutex(_Mtx)
	{
		_MyMutex.lock();
	}
	lock_guard(_Mutex & _Mtx, adopt_lock_t)
		: _MyMutex(_Mtx)
	{}
	// 在析構(gòu)lock_gard時(shí)解鎖
	~lock_guard() _NOEXCEPT
	{
		_MyMutex.unlock();
	}
	lock_guard(const lock_guard&) = delete;
	lock_guard& operator=(const lock_guard&) = delete;
private:
	_Mutex& _MyMutex;
};

解釋:

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

3、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所管理的互斥量的指針)

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

錯(cuò)誤示例:使用普通的條件變量

先讓打印偶數(shù)線程獲取到所資源,然后在條件變量下等待并將鎖資源釋放
打印奇數(shù)獲取到鎖進(jìn)行打印,打印后先喚醒在條件變量下等待的線程,再等待在并釋放鎖資源
再打印偶數(shù)線程被喚醒并競(jìng)爭(zhēng)到鎖資源,進(jìn)行打印…
#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;
	//存在時(shí)間片切出去的問題
	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í),時(shí)間片到了線程被切出去,再等到打印奇數(shù)線程執(zhí)行喚醒等待條件變量下的線程時(shí)沒有線程被喚醒,當(dāng)打印偶數(shù)線程時(shí)間片切回時(shí),依舊會(huì)等待在條件變量下,而此時(shí)打印奇數(shù)線程也等待在條件變量下,此時(shí)沒人進(jìn)行喚醒兩線程也就會(huì)一直進(jìn)行等待

效果:

正確示例:

#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ù)條件判斷是否需要進(jìn)行阻塞等待
			cout << std::this_thread::get_id() << ":" << i << endl;
			i+=2;
			flg = false;//更改條件變量-使得另一個(gè)線程執(zhí)行,該線程會(huì)等待住
			cv.notify_one();//進(jìn)行喚醒等待條件變量下的線程
		}
	});
	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ù)條件判斷是否需要進(jìn)行阻塞等待
			cout << std::this_thread::get_id() << ":" << i << endl;
			i+=2;
			flg = false;//更改條件變量-使得另一個(gè)線程執(zhí)行,該線程會(huì)等待住
			cv.notify_one();//進(jìn)行喚醒等待條件變量下的線程
		}
	});
	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表達(dá)式/包裝器/線程庫的文章就介紹到這了,更多相關(guān)C++11 lambda表達(dá)式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

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

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

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

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

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

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

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

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

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

    C語言中bool和float的用法實(shí)例解析

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

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

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

    c語言同名標(biāo)靶點(diǎn)自動(dòng)匹配算法實(shí)現(xiàn)實(shí)例代碼

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

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

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

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

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

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

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

最新評(píng)論