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

news2024/11/15 9:09:59

目录

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1164208.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SAFe大规模敏捷框架

Leangoo领歌是一款永久免费的专业的敏捷开发管理工具&#xff0c;提供端到端敏捷研发管理解决方案&#xff0c;涵盖敏捷需求管理、任务协同、进展跟踪、统计度量等。 
 Leangoo领歌上手快、实施成本低&#xff0c;可帮助企业快速落地敏捷&#xff0c;提质增效、缩短周期、加速…

与AI对话的艺术:如何优化Prompt以获得更好的响应反馈

前言 在当今数字化时代&#xff0c;人工智能系统已经成为我们生活的一部分。我们可以在智能助手、聊天机器人、搜索引擎等各种场合与AI进行对话。然而&#xff0c;要获得有益的回应&#xff0c;我们需要学会与AI进行有效的沟通&#xff0c;这就涉及到如何编写好的Prompt。 与…

【Linux学习笔记】进程概念(下)

进程地址空间1. 虚拟地址2. 什么是进程地址空间3. 进程地址空间的映射。4. 地址空间存在的意义5. 写时拷贝 进程地址空间 1. 虚拟地址 来看这样一段代码。 #include <stdio.h> #include <unistd.h>int global_value 100;int main() {pid_t id fork();if(id &l…

3206. 拼图

给出一个 nm 的方格图&#xff0c;现在要用如下 L 型的积木拼到这个图中&#xff0c;使得方格图正好被拼满&#xff0c;请问总共有多少种拼法。 其中&#xff0c;方格图的每一个方格正好能放积木中的一块。 积木可以任意旋转。 输入格式 输入的第一行包含两个整数 n,m&#xff…

scrapy+selenium框架模拟登录

目录 一、cookie和session实现登录原理 二、模拟登录方法-Requests模块Cookie实现登录 三、cookiesession实现登录并获取数据 四、selenium使用基本代码 五、scrapyselenium实现登录 一、cookie和session实现登录原理 cookie:1.网站持久保存在浏览器中的数据2.可以是长期…

3D视觉引导工业机器人上下料,助力汽车制造业实现智能化生产

在工业制造领域&#xff0c;机器人技术一直是推动生产效率和质量提升的重要力量。近年来&#xff0c;随着3D视觉技术的快速发展&#xff0c;工业机器人在处理复杂任务方面迈出了重要的一步。特别是在汽车制造行业&#xff0c;3D视觉引导工业机器人的应用已经取得了令人瞩目的成…

dockefile

文章目录 应用的部署MySql的部署Tomcat的部署 dockerfileDocker原理镜像的制作容器转镜像Dockerfile 服务编排Docker Compose Docker 私有仓库 应用的部署 搜索app的镜像拉去app的镜像创建容器操作容器中的app MySql的部署 容器内的网络服务和外部机器无法直接通信外部机器和…

软件测试 —— 移动端测试

1. 移动端 指移动设备&#xff08;如智能手机、平板电脑、智能手表等&#xff09;上的操作系统和应用程序。移动设备具有便携性和多功能性&#xff0c;可以随时随地连接互联网&#xff0c;提供丰富的应用和服务。 2. 移动端应用分类 (1) 原生应用&#xff08;Native App&…

访问单通道Mat中的值之at()、ptr()、iscontinuous()【C++的OpenCV 第十四课-OpenCV基础强化(二)】

&#x1f389;&#x1f389;&#x1f389; 欢迎各位来到小白 p i a o 的学习空间&#xff01; \color{red}{欢迎各位来到小白piao的学习空间&#xff01;} 欢迎各位来到小白piao的学习空间&#xff01;&#x1f389;&#x1f389;&#x1f389; 目录 一、访问的方法 \color{blu…

voronoi diagram(泰森多边形) 应用 - Empire Strikes Back

欢迎关注更多精彩 关注我&#xff0c;学习常用算法与数据结构&#xff0c;一题多解&#xff0c;降维打击。 voronoi 图求解点击前往 题目链接&#xff1a;https://vjudge.net/problem/URAL-1520 题目大意 有一个城市&#xff0c;形状是圆形。 城市里有很多化工场。 现在想…

matlab双目标定中基线物理长度获取

在MATLAB进行双目摄像机标定时,通常会获得相机的内参,其中包括像素单位的焦距(focal length)以及物理单位的基线长度(baseline)。对于应用中的深度估计和测量,基线长度的物理单位非常重要,因为它直接影响到深度信息的准确性。有时候,您可能只能获取像素单位的焦距和棋…

华为RS设备状态及接口配置命令

1、查看硬件信息 ①查看序列号 查看整机序列号 display esn display sn ②、查看功率 电源功率 display power 查看光模块功率 display transceiver interface gigabitethernet 1/0/0 verbose ③、查看风扇 display fan ④、查看温度 display temperature all ⑤、查看硬…

微信聚合聊天系统的便捷功能:自动发圈,跟圈

快到双十一咯&#xff0c;很多商家和自媒体、运营人都在发圈做运营&#xff0c;所以现在发圈的频率也会比以往的多一些&#xff0c;但事情一多就会担心今天的朋友圈忘记发、漏发或者错过发圈的时间导致错过私域里的好友、客户会错过活动时间。 其实这些都是可以不用担心&#…

Python 正则表达式(RegEx)指南

正则表达式&#xff08;RegEx&#xff09;是一系列字符&#xff0c;形成了一个搜索模式。RegEx 可用于检查字符串是否包含指定的搜索模式。 RegEx 模块 Python 中有一个内置的包叫做 re&#xff0c;它可以用于处理正则表达式。导入 re 模块&#xff1a; import rePython 中的…

怪物猎人世界Mod制作——替换模型、音效

太喜欢《怪物猎人&#xff1a;世界》这款游戏了&#xff0c;在冰原更新后&#xff0c;游戏版本趋于稳定。卡普空做一些bug修复后宣布不再更新此游戏&#xff0c;游戏版本稳定在v15.11.01。从此这个游戏长达三年未更新&#xff0c;我玩了八百小时也未发现什么明显BUG&#xff0c…

Linux多虚拟主机和配置限制访问与日志

目录 一、多虚拟主机 1.配置单网卡多个ip 2.给每个主机站点设置主页 3.测试访问 二、限制访问 1.限制所有 2.放行192.168.0.0/24网段访问 三、日志与状态页 1.定义访客日志 2.状态页配置 一、多虚拟主机 1.配置单网卡多个ip ip address add 192.168.0.231/24 dev e…

阿里云倚天实例已为数千家企业提供算力,性价比提升超30%

在2023云栖大会上&#xff0c;阿里云宣布倚天ECS实例已服务数千家企业&#xff0c;覆盖电商、生命科学、交通物流及游戏等领域&#xff0c;整体算力性价比提升超30%。 2022年&#xff0c;平头哥自研云原生CPU倚天710在阿里云数据中心规模化部署&#xff0c;并向云上企业提供算力…

“AI解析认知+大数据替代推荐”,云汉芯城推出【芯片智选】元器件替代查询工具

面对数以亿计的元器件型号&#xff0c;工程师们往往会遇到经常使用的物料需要替代但难以选型、遍查规格书也无法对应上所有参数&#xff0c;或是新找到的物料资料不全等问题。如何进行高效准确的选型与替代决策是一项极具挑战性的任务。 在此背景下&#xff0c;云汉芯城结合自研…

机器人制作开源方案 | 宠物智能机器人

一、作品简介 作者&#xff1a;陈瑛、卢文博、刘沈军、 浦津、葛望东单位&#xff1a;南京林业大学指导老师&#xff1a;金慧萍、田涛 1. 背景调研及研究意义 1.1背景调研 随着我国社会经济水平的飞速发展和城市化的进程加速推进&#xff0c;居民生活水平有了较 大幅度的提…

如何解决网站被攻击问题:高防服务器与高防CDN的选择

在当今数字时代&#xff0c;网站攻击已经成为严重的威胁&#xff0c;对网站的可用性和数据安全构成潜在风险。为了解决这个问题&#xff0c;企业需要考虑采用高防服务器或高防CDN等防护方案。本文将详细说明这两种方案的优劣势&#xff0c;并分析大、中、小型企业各自适合的防护…