【C++】C++11【下】lambda表达式|thread线程库

目录

1、lambda表达式

1.1 lambda表达式的引入

 1.2 lambda表达式的语法

1.3 lambda表达式的原理

 2、线程库

2.1thread类的介绍

2.2 线程函数参数

2.3 原子性操作库(atomic)

2.4 使用场景

应用场景1:

应用场景2:

应用场景3:

应用场景4:


本文为C++【下】

C++11【上】链接:【C++】C++11【上】列表初始化|声明|新容器|右值引用|完美转发|新的类功能-CSDN博客

1、lambda表达式

1.1 lambda表达式的引入

C++98中,如果想要对一个数据集合中的元素进行排序,可利用sort

下面利用sort的使用来引出lambda表达式,因为sort是个函数模板,故第三个参数可以接受函数指针,函数对象(仿函数),lambda表达式,这里先利用前两个来实现

#include <algorithm>
#include <functional>//当需要用到仿函数(函数对象)时用,但你不写也能编译运行成功

template<class T>
struct Greater
{
	bool operator()(const T& x1, const T& x2)
	{
		return x1 > x2;
	}
};

bool g2(const int& x1, const int& x2)
{
	return x1 > x2;
}
int main()
{
	int array[] = { 4,1,8,5,3,7,0,9,2,6 };
	// 默认按照小于比较,排出来结果是升序
	std::sort(array, array + sizeof(array) / sizeof(array[0]));
	// 如果需要降序,需要改变元素的比较规则
	//std::sort(array, array + sizeof(array) / sizeof(array[0]), Greater<int>());

	Greater<int> g1;
	g1(1, 2);	//g1是一个对象,调用它的operator()实现的
	g2(1, 2);	//g2是一个函数指针,调用它指向的函数
	
	//他们是完全不同的对象,但用起来是一样的,均可排序
	std::sort(array, array + sizeof(array) / sizeof(array[0]), g1);
	std::sort(array, array + sizeof(array) / sizeof(array[0]), g2);

	return 0;
}

如果待排序元素为自定义类型,需要用户定义排序时的比较规则:  

struct Goods
{
	string _name;  // 名字
	double _price; // 价格
	int _evaluate; // 评价
	Goods(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{}
};
struct ComparePriceLess
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price < gr._price;
	}
};
struct ComparePriceGreater
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price > gr._price;
	}
};
int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
   3 }, { "菠萝", 1.5, 4 } };

	sort(v.begin(), v.end(), ComparePriceLess());
	sort(v.begin(), v.end(), ComparePriceGreater());
}
随着 C++ 语法的发展, 人们觉得上面的写法太复杂了,每次为了实现一个 algorithm 算法, 都要重新去写一个类(仿函数或函数),如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名, 一般需要根据功能来命名(你瞎命名别人很难看懂) 。因此,在 C++11 语法中出现了 Lambda 表达式

 1.2 lambda表达式的语法

lambda表达式书写格式:

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

1. lambda表达式各部分说明:

①、[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]

判断接下来的代码是否为lambda函数捕捉列表能够捕捉上下文中的变量供lambda函数使用。

②、(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以

连同()一起省略

③、mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量

性。使用该修饰符时,参数列表不可省略(即使参数为空)。

④、->returntype返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回

值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导

⑤、{statement}函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获

到的变量。

注意:

在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为

。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情

int main()
{
	// 最简单的lambda表达式, 该lambda表达式没有任何意义
	[] {};

	// 省略参数列表和返回值类型,返回值类型由编译器推导为int
	int a = 3, b = 4;
	[=] {return a + 3; };

	// 省略了返回值类型,无返回值类型
	auto fun1 = [&](int c) {b = a + c; };
	fun1(10);
	cout << a << " " << b << endl; //3 13

	// 各部分都很完善的lambda函数
	auto fun2 = [=, &b](int c)->int {return b += a + c; };
	cout << fun2(10) << endl; //26

	// 捕捉x
	int x = 10;
	auto add_x = [x](int a) mutable { x *= 2; return a + x; };
	cout << add_x(10) << endl; //30
	return 0;
}

2. 捕获列表说明:

捕捉列表描述了上下文中哪些数据可以被 lambda 使用 ,以及 使用的方式传值还是传引用
【捕捉列表就是捕捉跟我同一个作用域的对象】
传值捕捉:[a]捕捉a [a,b]捕捉a,b  [=]捕捉同一作用域中的所有对象
传值捕捉的对象是不能被改变的,加上mutable就可以改变
传引用捕捉:[&a]捕捉a [&a,&b]捕捉a,b  [&]捕捉同一作用域中的所有对象
  • [var]:   表示值传递方式捕捉变量var
  • [=]:      表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:     表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:  表示值传递方式捕捉当前的this指针(其实=和&就已经包含this指针了)

下面利用lambda表达式实现相加和交换两数操作,了解lambda表达式的使用

int add1(int a, int b)
{
	return a + b;
}

int main()
{
	int a = 0, b = 1;
	//实现一个a+b的lambda表达式
	auto add1 = [](int x1, int x2)->int{return x1 + x2;};//返回值可不写,编译器会自动推(但不建议)
	cout << add1(a, b) << endl;
	cout << ::add1(a, b) << endl;//调用全局域的add1,即主函数之外的

	//auto add1 = [](int x1, int x2)->int {return x1 + x2 + a; };//没捕捉a导致无法使用
	auto add1 = [a](int x1, int x2)->int {return x1 + x2 + a; };//捕捉a后可以正常使用
	auto add1 = [a,b]()->int {return a + b; };//捕捉a,b,只用于a + b

	//实现a和b交换
	auto swap1 = [](int& x, int& y)
	{	int z = x;
		x = y;
		y = z; 
	};
	swap(a, b);

	//不正确的用法,即使加上mutable可以修改传值捕捉了,但传值捕捉的
	//是a和b的拷贝,只是把a和b的拷贝交换了而已,所以这里是没意义的
	auto swapab = [a, b]()mutable
	{
		int c = a;
		a = b;
		b = c;
	};
	swapab();

	return 0;
}
注意:
a. 父作用域指包含 lambda 函数的语句块
b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割
比如:
[=, &a, &b] :以引用传递的方式捕捉变量 a b ,值传递方式捕捉其他所有变量
[& a, this] :值传递方式捕捉变量 a this ,引用方式捕捉其他变量
c. 捕捉列表不允许变量重复传递,否则就会导致编译错误
比如: [=, a] = 已经以值传递方式捕捉了所有变量,捕捉 a 重复
d. 在块作用域以外的 lambda 函数捕捉列表必须为空
e. 在块作用域中的 lambda 函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者
非局部变量都会导致编译报错。
f. lambda 表达式之间不能相互赋值 ,即使看起来类型相同
void (*PF)();
int main()
{
	auto f1 = [] {cout << "hello world" << endl; };
	auto f2 = [] {cout << "hello world" << endl; };
	// 此处先不解释原因,等lambda表达式底层实现原理看完后,大家就清楚了
	//f1 = f2;   // 编译失败--->提示找不到operator=()
	// 允许使用一个lambda表达式拷贝构造一个新的副本
	auto f3(f2);
	f3();
	// 可以将lambda表达式赋值给相同类型的函数指针
	PF = f2;
	PF();
	return 0;
}

3.使用场景:

struct Goods
{
	string _name;	// 名字
	double _price; // 价格
	int _evaluate; // 评价
	Goods(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{}
};

int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
   3 }, { "菠萝", 1.5, 4 } };
	//因为sort是个函数模板,故第三个参数可以传仿函数,函数指针和lambda表达式
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._price < g2._price; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._price > g2._price; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._evaluate < g2._evaluate; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._evaluate > g2._evaluate; });
	
	//一般我们不会像下面这么用,我们一般都是构造lambda表达式的匿名对象
	/*auto price_greater = [](const Goods& g1, const Goods& g2)->bool {return g1._price <
		g2._price; };
	sort(v.begin(), v.end(), price_greater);*/
}

1.3 lambda表达式的原理

先看以下用仿函数和lambda表达式实现的代码:

class Rate
{
public:
	Rate(double rate) : _rate(rate)
	{}
	double operator()(double money, int year)
	{
		return money * _rate * year;
	}
private:
	double _rate;
};
int main()
{
	// 函数对象
	double rate = 0.49;
	Rate r1(rate);
	r1(10000, 2);//仿函数
	// lambda
	auto r2 = [=](double monty, int year)->double {return monty * rate * year;
		};
	r2(10000, 2);//lambda表达式
	return 0;
}
从使用方式上来看,函数对象与 lambda表达式完全一样。函数对象将 rate 作为其成员变量,在定义对象时给出初始值即可, lambda表达式通过捕获列表可以直接将该变量捕获到。
实际在底层编译器对于 lambda 表达式的处理方式,完全就是按照函数对象的方式处理的,即:如
果定义了一个 lambda 表达式,编译器会自动生成一个类,在该类中重载了 operator()
int main()
{
	int a = 1, b = 2;
	// 对象 = 对象(编译器生成的lambda_uuid仿函数的对象)
	auto add = [](int x, int y)->int {return x + y; };

	add(a,b); //call lambda_uuid仿函数的operator()

	//底层还是靠仿函数来实现,也就是说你定义了一个lambda表达式,实际上
	//编译器会全局域生成一个叫lambda_uuid类,仿函数的operator()的参数和实现
	//就是我们写的lambda表达式的参数和实现
	
	//仿函数和lambda表达式的关系如同迭代器和范围for的关系,也就是lambda表达式存在即合理

	return 0;
}

 2、线程库

2.1thread类的介绍

C++11 之前,涉及到多线程问题,都是和平台相关的,比如 windows linux 下各有自己的接 口,这使得代码的可移植性比较差 C++11 中最重要的特性就是对线程进行支持了,使得 C++ 并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念。要使用标准库中的 线程,必须包含 < thread > 头文件。

C++11 线程库
windows 自己的一套API         如:CreateThread
Linux   使用posix的pthread  如:pthread_create
C++98中,若你想写多线程的程序,既想在windows下跑,也想在Linux下跑,怎么办?
条件编译
#ifdef _WIN32
    CreateThread(...)
#else
    pthread_create(...)
#endif

C++11 线程库
特点:跨平台、面向对象封装的类(每个线程是一个类对象)
实现原理:封装库时使用了条件编译,也就是他的底层还是分别调用了不同平台的线程API

扩展:C++缺点之一:有用的东西更新的太慢了,比如线程库C++11(2011)才更新的,且到现在也没有更新一个官方的封装好的靠谱网络库,其次一堆使用意义不大的语法一堆,学习成本高

  • thread()构造一个线程对象,没有关联任何线程函数,即没有启动任何线程
  • thread(fn,args1,args2,参数…)构造一个线程对象,并关联线程函数fn,args1,args2,…为线程函数的参数
  • get_id()获取线程id
  • jionable()线程是否还在执行,joinable代表的是一个正在执行中的线程。
  • jion()该函数调用后会阻塞住线程,当该线程结束后,主线程继续执行
  • detach()在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离的线程变为后台线程,创建的线程的"死活"就与主线程无关
注意:
1. 线程是操作系统中的一个概念, 线程对象可以关联一个线程,用来控制线程以及获取线程的
状态
2. 当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程。
#include <thread>
int main()
{
 std::thread t1;
 cout << t1.get_id() << endl;
 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;
3. 当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。
线程函数一般情况下可按照以下三种方式提供:
  • 函数指针
  • 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 类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个
线程对象关联线程的状态转移给其他线程对象,转移期间不意向线程的执行。
5. 可以通过 jionable() 函数判断线程是否是有效的,如果是以下任意情况,则线程无效
  • 采用无参构造函数构造的线程对象
  • 线程对象的状态已经转移给其他线程对象
  • 线程已经调用jion或者detach结束

2.2 线程函数参数

线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的 ,因此:即使线程参数为引用类型,在
线程中修改后也不能修改外部实参,因为 其实际引用的是线程栈中的拷贝,而不是外部实参
#include <thread>
void ThreadFunc1(int& x)
{
	x += 10;
}
void ThreadFunc2(int* x)
{
	*x += 10;
}
int main()
{
	int a = 10;
	//在线程函数中对a修改,不会影响外部实参,因为:线程函数参数虽然是引用方式,但其实际
	//引用的是线程栈中的拷贝
	thread t1(ThreadFunc1, a);
	t1.join();
	cout << a << endl;
	// 如果想要通过形参改变外部实参时,必须借助std::ref()函数
	thread t2(ThreadFunc1, std::ref(a));
	t2.join();
	cout << a << endl;
	// 地址的拷贝
	thread t3(ThreadFunc2, &a);
	t3.join();
	cout << a << endl;
	return 0;
}
注意:如果是类成员函数作为线程参数时,必须将 this 作为线程函数参数。

2.3 原子性操作库(atomic)

多线程最主要的问题是共享数据带来的问题(即线程安全)。如果共享数据都是只读的,那么没问 题,因为只读操作不会影响到数据,更不会涉及对数据的修改,所以所有线程都会获得同样的数 据。但是,当一个或多个线程要修改共享数据时,就会产生很多潜在的麻烦。比如:

#include <thread>
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 中传统的解决方式:可以对共享修改的数据可以加锁保护
#include <thread>
#include <mutex>
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中引入了原子操作。
问:锁的串行效率高还是并行效率高?
答:串行效率高
mutex mtx;
int x = 0;

//两个线程一起对x加n次
void Add(int n)
{	
	//串行:一个线程跑完了,另一个线程接着跑
	mtx.lock();
	for (int i = 0; i < n; ++i)
	{
		++x;
	}
	mtx.unlock();

	//并行:两个线程同时跑
	/*for (int i = 0; i < n; ++i)
	{
		mtx.lock();// 比如t2刚切出去,t1就解锁了。++后马上又把t2切回来
		++x;
		mtx.unlock();
	}*/

	//实际上串行更快。为什么?因为这里锁的力度太小了,时间都花到切换上下文了。
}
原子操作:即不可被中断的一个或一系列操作, C++11引入 的原子操作类型,使得线程间数据的同步变得非常高效。
注意:需要使用以上原子操作变量时,必须添加头文件

 在 C++11 中, 程序员不需要对原子类型变量进行加锁解锁操作,线程能够对原子类型变量互斥的
访问
更为普遍的,程序员可以 使用 atomic 类模板,定义出需要的任意原子类型
atmoic<T> t;    // 声明一个类型为T的原子类型变量t

注意:原子类型通常属于"资源型"数据,多个线程只能访问单个原子类型的拷贝,因此C++11 中,原子类型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造、移动构造以及 operator=等,为了防止意外,标准库已经将atmoic模板类中的拷贝构造、移动构造、赋值运算 符重载默认删除掉了。

#include <atomic>
int main()
{
 atomic<int> a1(0);
 //atomic<int> a2(a1);   // 编译失败
 atomic<int> a2(0);
 //a2 = a1;               // 编译失败
 return 0;
}

2.4 使用场景

应用场景1:

两个线程一起对x加n次:thread各配合函数指针、仿函数、lambda表达式使用
①、函数指针
atomic<int> x = 0; //支持整形/浮点的原子++,--
//扩展学习:atomic支持CAS->无锁编程

//两个线程一起对x加n次
void Add(int n)
{
	for (int i = 0; i < n; ++i)
	{
		++x;
	}
}

int main()
{
 
 //利用函数指针配合thread
	thread t1(Add, 1000000);//加锁之后每次计算的出的数据就准确了
	thread t2(Add, 1000000);


	//要在主线程结束前对两个线程join一下才行
	t1.join();
	t2.join();

	cout << x << endl;

	return 0;
}

②、仿函数

atomic<int> x = 0;
struct Add
{
	int operator()(int n)
	{
		for (int i = 0; i < n; ++i)
		{
			++x;
		}

		return x;
	}
};
int main()
{
	Add add;

	//利用仿函数对象配合thread
	thread t1(add, 1000000);
	thread t2(add, 1000000);//也可以写为Add(),即用匿名对象

	//要在join前获取线程id
	cout << t1.get_id() << endl;//每次获取到的ID不一
	cout << t2.get_id() << endl;//每次获取到的ID不一

	t1.join();
	t2.join();

	cout << x << endl;//2000000

	return 0;
}

③、lambda表达式

int main()
{
	atomic<int> x = 0;

	auto add = [&x](int n) {
		for (int i = 0; i < n; ++i)
		{
			++x;
		}
		};

	thread t1(add, 1000000);
	thread t2(add, 1000000);//也可以写为Add(),即用匿名对象

	//要在join前获取线程id
	cout << t1.get_id() << endl;//每次获取的ID不一
	cout << t2.get_id() << endl;//每次获取的ID不一

	t1.join();
	t2.join();
	
	cout << x << endl;//2000000

	return 0;
}

应用场景2:

m个线程对x加n次

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

//写法二
int main()
{
	atomic<int> x = 0;
	//m个线程对x加n次
	int m, n;
	cin >> m >> n;

	//thread支持移动赋值和移动拷贝,不支持深拷贝的拷贝构造和拷贝赋值
	vector<thread> vthreads(m);
	for (int i = 0; i < m; ++i)
	{
		//移动赋值【thread构造的是匿名对象,返回右值】
		vthreads[i] = 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;
}

应用场景3:

使用两个线程打印0~n之间的数,一个线程打印奇数,一个线程打印偶数,要求依次打印。
//以下代码存在一个问题:竞争终端,每次结果都不一样,且都不对
int main()
{
	int n = 100;
	thread t1([n]()
	{
		for (int i = 0; i < n; ++i)
		{
			//偶数
			if (i % 2 == 0)
				cout << this_thread::get_id() << ":" << i << endl;
		}
	});

	thread t2([n]()
		{
			for (int i = 0; i < n; ++i)
			{
				//奇数
				if (i % 2)
					cout << this_thread::get_id() << ":" << i << endl;//利用this_thread可以不需要线程对象就获取到id
			}
		});

	t1.join();
	t2.join();

	return 0;
}

​​​​​​改善后:

#include<mutex>
#include<condition_variable>

//偶数和奇数依次打印的过程类似于生产者消费者模型
//互斥锁和条件变量同时使用
int main()
{
	int n = 100;
	mutex mtx1, mtx2;
	condition_variable cv1, cv2;//条件变量(一个是不行的,要让偶数先走),必须配合锁用


	thread t1([&]()
		{
			for (int i = 0; i < n; i+=2)
			{	if (i != 0)
					cv1.wait(unique_lock<mutex>(mtx1));

				//偶数
				cout << this_thread::get_id() << ":" << i << endl;

				cv2.notify_one(); //t1打印偶数以后,通知t2
			}
		});

	thread t2([&]()
		{
			for (int i = 0; i < n; i+=2)
			{
				cv2.wait(unique_lock<mutex>(mtx2));//要传互斥锁来保护条件变量
				//奇数
				cout << this_thread::get_id() << ":" << i << endl;//利用this_thread可以不需要线程对象就获取到id
				cv1.notify_one(); //t2
			}
		});

	t1.join();
	t2.join();

	return 0;
}

//法二
int main()
{
	int n = 100;
	mutex mtx1, mtx2;
	condition_variable cv1, cv2;//条件变量(一个是不行的,要让偶数先走),必须配合锁用


	thread t1([&]()
		{
			for (int i = 0; i < n; i += 2)
			{

				//偶数
				cout << this_thread::get_id() << ":" << i << endl;

				cv2.notify_one(); //t1打印偶数以后,通知t2
				cv1.wait(unique_lock<mutex>(mtx1));

			}
		});

	thread t2([&]()
		{
			for (int i = 0; i < n; i += 2)
			{
				cv2.wait(unique_lock<mutex>(mtx2));//要传互斥锁来保护条件变量
				//奇数
				cout << this_thread::get_id() << ":" << i << endl;//利用this_thread可以不需要线程对象就获取到id
				cv1.notify_one(); //t2
			}
		});

	t1.join();
	t2.join();

	return 0;
}

应用场景4:

线程池

//扩展:线程池
struct tack
{
	template<class fn>
	tack(fn)
	{}
};

class thread_poo1
{
public:
	thread_poo1(int n = 8)
		:vthreads(n)
	{}

private:
	vector<thread> vthreads;
	//queue<task> _q;
};

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值