【C++】C++11:线程库和包装器

这篇具有很好参考价值的文章主要介绍了【C++】C++11:线程库和包装器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

C++11最后一篇文章

文章目录

  • 前言
  • 一、线程库
  • 二、包装器和绑定
  • 总结

前言

上一篇文章中我们详细讲解了lambda表达式的使用,我们今天所用的线程相关的知识会大量的用到lambda表达式,所以对lambda表达式还模糊不清的可以先将上一篇文章看明白。


一、线程库

C++11 之前,涉及到多线程问题,都是和平台相关的,比如 windows linux 下各有自己的接
口,这使得代码的可移植性比较差 C++11 中最重要的特性就是对线程进行支持了,使得 C++
并行编程时不需要依赖第三方库 ,而且在原子操作中还引入了原子类的概念。要使用标准库中的
线程,必须包含 < thread > 头文件。
thread():构造一个线程对象,没有关联任何线程函数,即没有启动任何线程
thread(fn, args1, args2, ...):构造一个线程对象,并关联线程函数fnargs1args2...为线程函数的参数
get_id():获取线程id
joinable():线程是否还在执行,joinable代表的是一个正在执行中的线程。
join():该函数调用后会阻塞住线程,当该线程结束后,主线程继续执行
detach():在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离的线程变为后台线程,创建的线程的"死活"就与主线程无关.
注意:
1. 线程是操作系统中的一个概念, 线程对象可以关联一个线程,用来控制线程以及获取线程的
状态
2. 当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程。
下面我们实现用m个线程分别打印n个数:
int main()
{
	size_t m;
	cin >> m;
	vector<thread> vth(m);
	for (int i = 0; i < m; i++)
	{
		size_t n = 0;
		cin >> n;
		vth[i] = thread([n,i]()
			{
				for (int j = 0; j < n; j++)
				{
					cout << "我是第" << i << "个线程,打印:" << j + 1 << endl;
				}
				cout << endl;
			});
	}
	for (auto& t : vth)
	{
		t.join();
	}
	return 0;
}

上述代码最巧妙的一点就是将线程放到了vector中,每次用一个匿名的线程对象利用lambda函数将这个线程要执行的函数给向量中的线程,然后完成用m个线程打印不同的n个数,在结束前一定要将线程等待回收了。下面我们运行起来:

【C++】C++11:线程库和包装器

 可以看到成功完成了任务,在这个程序中我们用到了线程的构造函数和join函数,也体现了lambda函数的好用。

下面我们演示一下的get_id接口的使用:
【C++】C++11:线程库和包装器

【C++】C++11:线程库和包装器 这样使用不知道大家会不会感到别扭呢,实际上在我们日常使用中是这样使用的:

【C++】C++11:线程库和包装器

【C++】C++11:线程库和包装器

 this_thread是一个命名空间,在这个命名空间中也有线程先关的函数,并且这里的函数不依靠线程对象来调用,哪个线程调用这个函数就打印哪个线程的ID。下面我们可以再用用this_thread这个命名空间中的其他接口:

【C++】C++11:线程库和包装器

 sleep_until是让这个线程休眠到某个时间,sleep_for是相对时间,这个接口是最常用的接口。下面我们演示一下sleep_for接口:

【C++】C++11:线程库和包装器

 在官方文档演示中我们可以看到有一个chrono的命名空间:

【C++】C++11:线程库和包装器

 这个命名空间中主要是一些时间函数的使用:

【C++】C++11:线程库和包装器

【C++】C++11:线程库和包装器

 可以看到确实打印完成后等待2秒再运行下一句指令,我们再试试微秒:

【C++】C++11:线程库和包装器

 这里就不再演示了,大家感兴趣的可以在自己的编译器上测试。yield接口是主动让出时间片,在无锁编程中会用到这个接口。

总结:

当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。
线程函数一般情况下可按照以下三种方式提供:
1.函数指针
2.lambda表达式
3.函数对象
4. thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个
线程对象关联线程的状态转移给其他线程对象,转移期间不意向线程的执行。
5. 可以通过jionable()函数判断线程是否是有效的,如果是以下任意情况,则线程无效
采用无参构造函数构造的线程对象
线程对象的状态已经转移给其他线程对象
线程已经调用jion或者detach结束

下面我们讲解原子性操作:

int x = 0;

void func(int n)
{
	for (int i = 0; i < n; i++)
	{
		++x;
	}
}
int main()
{
	thread t1(func, 10000);
	thread t2(func, 20000);
	t1.join();
	t2.join();
	cout << x << endl;
	return 0;
}

首先上面的代码中有一个全局变量x,然后两个线程都调用func函数让x++,在我们看来x最后的结果应该是30000才对,那么结果是不是这样呢我们看一下:

【C++】C++11:线程库和包装器

 我们可以看到结果是不对的,这就是经典的线程安全问题。解决线程安全的方法有:CAS,加锁以及原子性操作,下面我们先用加锁演示一下:

【C++】C++11:线程库和包装器

#include <mutex>
int x = 0;
mutex mtx;
void func(int n)
{
	mtx.lock();
	for (int i = 0; i < n; i++)
	{
		++x;
	}
	mtx.unlock();
}
int main()
{
	thread t1(func, 10000);
	thread t2(func, 20000);
	t1.join();
	t2.join();
	cout << x << endl;
	return 0;
}

 首先我们的锁一定是一个全局的,因为要让两个线程看到同一把锁,如果我们将锁放在func函数内部,那么两个线程用的两把锁是没作用的。

【C++】C++11:线程库和包装器

 可以看到确实解决了刚刚的问题,当然我们也可以在进入循环的时候加锁:

void func(int n)
{
	for (int i = 0; i < n; i++)
	{
		mtx.lock();
		++x;
		mtx.unlock();
	}
}

【C++】C++11:线程库和包装器

 那么这两种加锁方式有什么区别呢?首先我们刚刚演示的第一种方法是串行的,也就是说当一个线程时间片到了后才会让下一个线程进入for循环,第二个方法是并行的,两个线程都可以进入func函数,进入for循环后谁先申请到锁谁就可以++x变量,下面我们可以看看哪一种方法的效率高:

#include <mutex>
int x = 0;
mutex mtx;
void func(int n)
{
	mtx.lock();
	for (int i = 0; i < n; i++)
	{
		++x;
	}
	mtx.unlock();
}
void func(int n)
{
	for (int i = 0; i < n; i++)
	{
		mtx.lock();
		++x;
		mtx.unlock();
	}
}
int main()
{
	int n = 100000;
	size_t begin = clock();
	thread t1(func, n);
	thread t2(func, n);
	t1.join();
	t2.join();
	size_t end = clock();
	cout << x << endl;
	cout << "--------------------------------" << endl;
	cout << end - begin << endl;
	return 0;
}
我们在每次运行前记录一下时间,结束后再记录一下时间,最后看哪种方式的时间少谁的效率就高:
【C++】C++11:线程库和包装器

串行的方式是2秒

【C++】C++11:线程库和包装器并行的方式是30秒。为什么在这种情况下串行的效率更高呢?这是因为并行的方式每次解锁后都会频繁的切换线程的上下文,这会浪费很多的时间。

下面我们用lambda表达式改造一下刚刚的代码:

int main()
{
	int n = 100000;
	int x = 0;
	mutex mtx;
	size_t begin = clock();
	thread t1([&, n]()
		{
			mtx.lock();
			for (int i = 0; i < n; i++)
			{
				++x;
			}
			mtx.unlock();
		});
	thread t2([&, n]()
		{
			mtx.lock();
			for (int i = 0; i < n; i++)
			{
				++x;
			}
			mtx.unlock();
		});
	t1.join();
	t2.join();
	size_t end = clock();
	cout << end - begin << endl;
	return 0;
}

 我们从文档中可以看到,c++中的锁有多种:

【C++】C++11:线程库和包装器

 recursive_mutex是递归锁,下面我们演示一下:

int x = 0;
void func(int n)
{
	if (n == 0)
	{
		return;
	}
	++x;
	func(n - 1);
}
int main()
{
	thread t1(func, 1000);
	thread t2(func, 200);
	t1.join();
	t2.join();
	cout << x << endl;
	return 0;
}
【C++】C++11:线程库和包装器

 在这种递归的情况下官方更推荐使用递归锁,如下图:

int x = 0;
recursive_mutex mtx;
void func(int n)
{
	if (n == 0)
	{
		return;
	}
	mtx.lock();
	++x;
	mtx.unlock();
	func(n - 1);
}
int main()
{
	thread t1(func, 1000);
	thread t2(func, 200);
	t1.join();
	t2.join();
	cout << x << endl;
	return 0;
}
【C++】C++11:线程库和包装器

注意:我们的锁一定不可以加到if判断语句前面

【C++】C++11:线程库和包装器 因为一旦在判断语句前面加锁那么一定会有线程无法进入递归结束条件从而造成死递归。同样我们也不能在递归后(func(n-1))后面解锁,这样就造成了加锁后还没解锁就进入递归下一层栈帧,下一次进入此函数又重新加锁,这样就造成了死锁问题。

下面我们再看看抛异常期间出现的经典死锁问题:

int x = 0;
mutex mtx;
void func(int n)
{
	for (int i = 0; i < n; i++)
	{
		try
		{
			mtx.lock();
			++x;
			//抛异常
			if (rand() % 3 == 0)
			{
				throw exception("抛异常");
			}
			mtx.unlock();
		}
		catch (const exception& e)
		{
			cout << e.what() << endl;
		}
	}
}
int main()
{
	thread t1(func, 10000);
	thread t2(func, 20000);
	t1.join();
	t2.join();
	return 0;
}

 当线程进入func函数后,我们手动设置了一个异常只要随机数%3等于0就直接抛异常,在这之前我们刚刚加了锁,抛异常进入catch后跳过了解锁步骤导致死锁问题,下面我们运行一下看是不是这样:

【C++】C++11:线程库和包装器

 可以看到确实是这样,那么如何解决这样的问题呢?

【C++】C++11:线程库和包装器

 在锁的文档中我们可以看到有这样两个接口,lock_guard接口就能解决我们的问题:

【C++】C++11:线程库和包装器

 lock_guard实际上就是一个创建了自动加锁,生命周期到了自动解锁的类对象,其实我们在讲linux线程的时候已经实现过了,感兴趣的可以看看我们linux多线程的文章。下面我们用一下这个借口:

template <class Lock>
class LockGuard
{
public:
	LockGuard(Lock& lk)
		:_lk(lk)
	{
		_lk.lock();
	}
	~LockGuard()
	{
		_lk.unlock();
	}
private:
	Lock _lk;
};
【C++】C++11:线程库和包装器

 【C++】C++11:线程库和包装器

 我们可以看到这次就不会死循环的打印了,解锁后自动退出程序了。解锁的原理的也很简单,当我们这个lock对象出了try这个作用域后就自动销毁解锁了,所以就解决了我们上面那个抛异常造成死锁的问题。

下面我们再试试unique_lock接口,这个接口与lock_guard都是RAII类型,但是unique_lock接口的功能会更多,如下图:
【C++】C++11:线程库和包装器

【C++】C++11:线程库和包装器 可以看到也可以解决刚刚抛异常出现的问题。

下面我们看看官网中atomic的接口:

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

 【C++】C++11:线程库和包装器

atomic接口都是封装了CAS的接口,下面我们演示一下:

int main()
{
	atomic<int> x = 0;
	int n = 10000;
	thread t1([&,n]()
		{
			for (int i = 0; i < n; i++)
			{
				++x;
			}
		});
	thread t2([&, n]()
		{
			for (int i = 0; i < n; i++)
			{
				++x;
			}
		});
	t1.join();
	t2.join();
	return 0;
}

 那么原子性的值该如何打印呢?

【C++】C++11:线程库和包装器

 实际上对于原子的变量有一个load接口是专门用来打印的:

【C++】C++11:线程库和包装器

 对于其他原子的接口我们就不在一一演示了,下面我们讲一个经典面试题:两个线程,一个线程打印奇数一个线程打印偶数交替打印。

在解决这个问题的时候我们需要用到条件变量,条件变量看过linux线程那篇文章的应该很熟悉,与那里是一模一样的,下面我们直接展示c++中条件变量的接口:

【C++】C++11:线程库和包装器条件变量有3个常用的接口,wait是让一个线程进入休眠,休眠期间会释放锁。one是唤醒一个线程,唤醒后会自动恢复之前休眠期间释放的锁,all是唤醒所有休眠的线程。

【C++】C++11:线程库和包装器

 通过文档我们可以看到条件变量是需要配合互斥锁使用的,因为条件变量本身并不是线程安全的,这里的条件变量的构造函数是用的unique_lock这个锁来初始化的,所以我们传参数的时候一定要看清楚。

【C++】C++11:线程库和包装器

 唤醒一个线程或者唤醒多个线程的接口是不需要参数的,直接使用接口就好,下面我们先写出交替打印奇偶数的框架:

int main()
{
	int n = 100;
	int x = 1;
	mutex mtx;
	thread t1([&, n]()
		{
			while (x < n)
			{
				cout << this_thread::get_id() << ": " << x << endl;
				++x;
			}
		});
	thread t2([&, n]()
		{
			while (x < n)
			{
				cout << this_thread::get_id() << ": " << x << endl;
				++x;
			}
		});
	t1.join();
	t2.join();
	return 0;
}

【C++】C++11:线程库和包装器

 通过结果我们可以看到是完不成我们给的交替打印的任务的,那么我们在加锁试一下:

【C++】C++11:线程库和包装器

【C++】C++11:线程库和包装器

 通过结果也可以看到是无法完成我们的任务的,下面我们就加上条件变量来试一下:

int main()
{
	int n = 100;
	int x = 1;
	mutex mtx;
	condition_variable cv;
	thread t1([&, n]()
		{
			while (x < n)
			{
				unique_lock<mutex> lock(mtx);
				if (x % 2 == 0)  //偶数就进去等待
				{
					cv.wait(lock);
				}
				cout << this_thread::get_id() << ": " << x << endl;
				++x;
				//唤醒等待的线程
				cv.notify_one();
			}
		});
	thread t2([&, n]()
		{
			while (x < n)
			{
				unique_lock<mutex> lock(mtx);
				if (x % 2 != 0)  //遇到奇数就等待
				{
					cv.wait(lock);
				}
				cout << this_thread::get_id() << ": " << x << endl;
				++x;
				//唤醒另一个休眠的线程
				cv.notify_one();
			}
		});
	t1.join();
	t2.join();
	return 0;
}

【C++】C++11:线程库和包装器

 通过结果我们可以看到已经完成了交替打印的任务,通过这个程序可以让我们学习到条件变量的使用,下面我们讲解一下这个程序:

首先我们通过unique_lock来加锁保证了t1先运行,t2阻塞。为什么能保证呢?1.t1先抢到锁,t2后抢到锁,t1先运行,t2阻塞在锁上面。2.t2先抢到锁,t1后抢到锁,t2先运行,t1阻塞在锁上面,但是t2会被下一步wait阻塞,并且wait的时候会解锁,保证了t1先运行。

我们用if判断可以防止一个线程不断运行,下面我们分析一下:

假设t1先获取到锁,t2后获取到锁,t2阻塞在锁上面。然后t1打印奇数,++x,x变成偶数,然后t1唤醒,但是没有线程wait,然后t1出了作用域解锁,然后t1的时间片到了切了出去。然后t2获取到锁打印,然后notify,但是没有线程等待lock出作用域解锁。假设t2的时间片充足再次获取到锁,如果没有条件控制,就会导致t2连续打印。

当然我们的程序还有一个小风险,当我们数++到100的时候很有可能进不去while判断条件,所以我们修改一下:

int main()
{
	int n = 100;
	int x = 1;
	mutex mtx;
	condition_variable cv;
	thread t1([&, n]()
		{
			while (1)
			{
				unique_lock<mutex> lock(mtx);
				if (x >= 100)
				{
					break;
				}
				if (x % 2 == 0)  //偶数就进去等待
				{
					cv.wait(lock);
				}
				cout << this_thread::get_id() << ": " << x << endl;
				++x;
				//唤醒等待的线程
				cv.notify_one();
			}
		});
	thread t2([&, n]()
		{
			while (1)
			{
				unique_lock<mutex> lock(mtx);
				if (x > 100)
				{
					break;
				}
				if (x % 2 != 0)  //遇到奇数就等待
				{
					cv.wait(lock);
				}
				cout << this_thread::get_id() << ": " << x << endl;
				++x;
				//唤醒另一个休眠的线程
				cv.notify_one();
			}
		});
	t1.join();
	t2.join();
	return 0;
}
以上就是线程相关的所有内容了。

二、包装器

function 包装器
function 包装器 也叫作适配器。 C++ 中的 function 本质是一个类模板,也是一个包装器。
【C++】C++11:线程库和包装器

 上图是包装器的声明,可以看到包装器的模板非常怪,下面我们就直接演示如何使用包装器了。

int f(int a, int b)
{
	return a + b;
}
struct Functor
{
public:
	int operator()(int a, int b)
	{
		return a + b;
	}
};
int main()
{
	int(*ptf)(int, int) = f;
	return 0;
}

首先像上面的代码我们用函数指针接收f函数会显得很麻烦,因为函数指针本身就很繁琐,下面我们看看包装器是如何接收的:

#include <functional>
int f(int a, int b)
{
	return a + b;
}
struct Functor
{
public:
	int operator()(int a, int b)
	{
		return a + b;
	}
};
int main()
{
	function<int(int, int)> f1 = f;
	function<int(int, int)> f2 = f;
	return 0;
}

【C++】C++11:线程库和包装器

 我们可以看到使用非常简单,甚至还可以使用lambda:

【C++】C++11:线程库和包装器

【C++】C++11:线程库和包装器 包装器的使用非常简单,就像函数一样。而且我们还可以将包装器当成函数声明:

int main()
{
	function<int(int, int)> f1 = f;
	function<int(int, int)> f2 = Functor();
	function<int(int, int)> f3 = [](int a, int b) { return a + b; };
	cout << f1(10, 20) << endl;
	cout << f2(30, 40) << endl;
	cout << f3(50, 50) << endl;
	map<string, function<int(int, int)>> sop;
	sop["函数指针"] = f1;
	sop["仿函数"] = f2;
	sop["lambda"] = f3;
	return 0;
}

【C++】C++11:线程库和包装器

下面我们演示一下包装器包装成员函数:

class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return a + b;
	}
};
int main()
{
	//静态成员函数
	function<int(int, int)> f1 = Plus::plusi;
	//普通成员函数
	function<double(Plus,double, double)> f2 = &Plus::plusd;
	return 0;
}

静态成员函数与刚刚的使用没有区别,主要是普通成员函数,因为普通成员函数参数多了一个this指针,并且语法是:域名前面必须加&

【C++】C++11:线程库和包装器

 当然这里不仅可以传类对象,也可以传指针。

【C++】C++11:线程库和包装器

【C++】C++11:线程库和包装器

那么包装器的最大作用是什么呢?那就是统一类型。

int f(int a, int b)
{
	return a + b;
}
class Plus
{
public:
	int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return a + b;
	}
};
int main()
{
	Plus plus;
	function<int(int, int)> f2 = f;
	//正常对于成员函数需要多一个参数
	function<int(Plus,int, int)> f1 = &Plus::plusi;
	//和25行统一参数类型
	function<int(int, int)>f3 = [&](int a, int b) {return plus.plusi(a,b); };
	return 0;
}

可以看到本来成员函数需要多一个参数的,但是我们直接捕捉了plus对象然后调用这个对象中的函数就完成了类型统一(都是int(int,int))

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()
{
	// 函数名
	cout << useF(f, 11.11) << endl;
	// 函数对象
	cout << useF(Functor(), 11.11) << endl;
	// lamber表达式
	cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
	return 0;
}

下面我们将上面的这段代码进行一下类型统一,让useF的类型都是相同的。

int main()
{
	cout << useF(function<double(double)>(f), 11.11) << endl;
	Functor func;
	cout << useF(function<double(double)>(func), 11.11) << endl;
	cout << useF(function<double(double)>([](double d) {return d / 4; }), 11.11);
	return 0;
}

可以看到我们useF使用起来参数都是相同这样在模板实例化的时候只会实例化一份而不是像上面那样一次实例化三个函数浪费多余的空间。

绑定

std::bind 函数定义在头文件中, 是一个函数模板,它就像一个函数包装器 ( 适配器 ) 接受一个可
调用对象( callable object ),生成一个新的可调用对象来 适应 原对象的参数列表 。一般而
言,我们用它可以把一个原本接收 N 个参数的函数 fn ,通过绑定一些参数,返回一个接收 M 个( M
可以大于 N ,但这么做没什么意义)参数的新函数。同时,使用 std::bind 函数还可以实现参数顺
序调整等操作。
下面我们看看绑定的原型:
// 原型如下:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
// with return type (2) 
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
可以将 bind 函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对
象来 适应 原对象的参数列表。
调用 bind 的一般形式: auto newCallable = bind(callable,arg_list);
其中, newCallable 本身是一个可调用对象, arg_list 是一个逗号分隔的参数列表,对应给定的
callable 的参数。 当我们调用 newCallable 时, newCallable 会调用 callable, 并传给它 arg_list
的参数
arg_list 中的参数可能包含形如 _n 的名字,其中 n 是一个整数,这些参数是 占位符 ,表示
newCallable 的参数,它们占据了传递给 newCallable 的参数的 位置 。数值 n 表示生成的可调用对
象中参数的位置: _1 newCallable 的第一个参数, _2 为第二个参数,以此类推。
下面我们用代码演示一下:
int ssub(int a, int b)
{
	return a - b;
}
class Sub
{
public:
	int sub(int a, int b)
	{
		return a - b;
	}
};

int main()
{
	function<int(int, int)> f = bind(ssub, placeholders::_1, placeholders::_2);
	cout << f(10, 20) << endl;
	function<int(int, int)> f1 = bind(ssub, placeholders::_2, placeholders::_1);
	cout << f1(10, 20) << endl;
	return 0;
}

【C++】C++11:线程库和包装器

 我们可以看到当我们将顺序改变后结果就不一样了,那么bind和placeholders是什么呢?

【C++】C++11:线程库和包装器

通过查询文档可以知道placeholders是一个命名空间:

【C++】C++11:线程库和包装器 _1,_2这些代表一个个的占位对象,比如我们函数的第一个参数就应该放在_1中,当我们将占位对象的位置发生改变后函数参数的顺序也相应的发生改变。那么有什么作用呢?还记得我们sort这类排序需要仿函数控制,那么我们如果是小于的比较当我们将顺序互换后其实就变成相反的结果了:

int main()
{
	function<bool(int, int)> f2 = bind(less<int>(), placeholders::_1, placeholders::_2);
	cout << f2(1, 2) << endl;
	function<bool(int, int)> f3 = bind(less<int>(), placeholders::_2, placeholders::_1);
	cout << f3(1, 2) << endl;
	return 0;
}

【C++】C++11:线程库和包装器

 当然我们在大多数情况下都不会去使用绑定的,所以大家只需要认识一下就可以了。当然对于成员函数我们需要有一些改变,如果我们不想在包装器中传第一个this指针参数的话那么可以直接绑定一下:

int main()
{
	function<int(int, int)> f = bind(&Sub::sub,Sub(), placeholders::_1, placeholders::_2);
	cout << f(10, 20) << endl;
	function<int(int, int)> f1 = bind(&Sub::sub,Sub(), placeholders::_2, placeholders::_1);
	cout << f1(10, 20) << endl;
	return 0;
}

 以上就是包装器和绑定的所有内容了,这部分内容可以了解一下,当需要使用的时候查一下文档即可,因为日常会很少使用。


总结

本篇文章中最重要的部分就是线程库了,学过linux的都知道linux下的线程使用起来是比c++中麻烦的,而c++线程库中将很多东西进行了封装就像简单的锁我们不需要自己去释放,出了作用域这个锁就自动销毁了。文章来源地址https://www.toymoban.com/news/detail-498648.html

到了这里,关于【C++】C++11:线程库和包装器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 【C++】c++11新特性(二)--Lambda函数及function(包装器)

    目录 Lambda函数 基本概念 基本语法 lambda 捕获(capture) 1. 不捕获任何变量 2. 按值捕获 3. 按引用捕获 4. 混合捕获 5. 捕获this 指针 包装器 function 基本概念 使用场景 1. 给function对象赋值  2. 作为函数参数和返回值   3. 存储在容器中 4. 绑定成员函数和带参数的函数        la

    2024年04月27日
    浏览(27)
  • 【C++进阶】C++11(下)可变参数模板&lambda表达式&包装器

    【C++进阶】C++11(下)可变参数模板&lambda表达式&包装器

    我们紧接着上一节的讲解来进行 C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧

    2024年04月11日
    浏览(18)
  • 【C++】—— C++11之线程库

    【C++】—— C++11之线程库

    前言: 在本期,我将给大家介绍的是 C++11 中新引进的知识,即关于 线程库 的相关知识。 目录 (一)线程库的介绍 1、线程库的由来 2、线程库的简单介绍 (二)线程函数参数 (三)原子性操作库 (四)lock_guard与unique_lock 1、mutex的种类 2、lock_guard 3、unique_lock (五)condi

    2024年02月11日
    浏览(5)
  • 包装类、多线程的基本使用

    基本类型 包装类 byte Byte short Short int Integer long Long float Float double Double char Character boolean Boolean 2.1Integer基本使用 Boolean包装类中 , 底层调用了parseBoolean(String s) , 在parseBoolean中调用了equals.IgnoreCase(s) , 忽略了大小写 2.2装箱and拆箱 2.3自动拆箱装箱 在操作的过程中,基本类型和包装

    2024年02月09日
    浏览(12)
  • 【C++入门到精通】 线程库 | thread类 C++11 [ C++入门 ]

    【C++入门到精通】 线程库 | thread类 C++11 [ C++入门 ]

    当讨论现代编程语言的特性时,C++11无疑是一个不容忽视的里程碑。在前一篇文章中,我们深入探讨了Lambda表达式在C++11中的引入和应用。本文将继续探索C++11的强大功能,具体而言,我们这篇文章将聚焦于线程库和其中的thread类。 线程在多任务处理中起着至关重要的作用,它

    2024年02月04日
    浏览(8)
  • 【C++】C++11线程库 和 C++IO流

    【C++】C++11线程库 和 C++IO流

    春风若有怜花意,可否许我再少年。 1. C++11的线程库实际封装了windows和linux底层的原生线程库接口,在不同的操作系统下运行时,C++11线程库可以通过条件编译的方式来适配的使用不同的接口,比如在linux下,就用封装POSIX线程库的接口来进行多线程编程,在windows下,就用封装

    2024年02月07日
    浏览(8)
  • C++之C++11 thread线程示例(一百三十八)

    C++之C++11 thread线程示例(一百三十八)

    简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏: Audio工程师进阶系列 【 原创干货持续更新中…… 】🚀 人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药. 更多原创,欢迎关注:An

    2023年04月15日
    浏览(10)
  • 【C++11】 包装器 | bind

    【C++11】 包装器 | bind

    function包装器 也被叫做 适配器 C++11中function本质是 类模板, 也是一个包装器 意义在于 对可调用对象类型进行封装再适配 可调用对象:函数指针 / lambda / 仿函数 需要包含 头文件 functional 模板参数 Ret : 被调用函数的返回类型 …Args作为参数包,这个参数包中包含0到任意个模

    2024年02月12日
    浏览(12)
  • C++11 function包装器

    C++11 function包装器

    在C++中,有三种 可调用对象 :函数指针,仿函数,lambda表达式。 三者有相似的作用和效果,但使用形式有很大的差异。 为了进行统一,C++11引进了 function包装器 首先,要想使用function,需要包含functional这个头文件 function包装器本质是一个 类模板 以往,如果要实现一个加法

    2024年02月13日
    浏览(15)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包