C++11新特性(5):多线程

学习C++11,根据网上资料的知识总结。

1. 线程创建

1.1 初始函数

#include 
#include 
void myfunc(int &a) 
{
	cout << "a in myfunc:" << a++ << endl;
}

int main()
{
	int a = 1;

	std::thread mythread(myfunc, std::ref(a)); //带引用参数
	mythread.join();//阻塞

	cout << "a in main:" << a << endl;

	system("pause");
	return 0;
}

运行结果:

C++11新特性(5):多线程_第1张图片

1.2 类对象

      需要重载()运算符,不然编译错误。

#include 
#include 
class Task
{
	static int a;
private:
	int v;
public:
	Task() 
	{
		v = a++;
	}
public:
	void operator()()  //()重载
	{
		cout << "v:" << v<< endl;
	}
};
int Task::a = 0;
int main()
{
	for (int i = 0; i < 4; i++)
	{
		Task task;
		thread t(task);
		t.detach();
	}

	system("pause");
	return 0;
}

    运行结果不唯一,因为没有阻塞:

C++11新特性(5):多线程_第2张图片

1.3 Lambda表达式

#include 
int main()
{
	auto myLambda = [](int *a){
		for (int i = 0; i < 10; ++i)
		{
			cout << *a << endl;
		}
	};

	[myLambda]{
		int a = 10;
		thread mythread(myLambda,&a);
		mythread.detach();
	}();

	system("pause");
	return 0;
}

  运行结果:

C++11新特性(5):多线程_第3张图片

        在Lambda线程中使用了局部变量a的指针,并且将该线程的运行方式设置为detach。这样,在lambda表达式执行结束后,变量a已经被销毁,但后台运行的线程仍然在使用已销毁变量a的指针,因此后面输出的值都是错误的。

        以detach方式执行线程时,最好将线程访问的局部数据复制到线程的空间(使用值传递),确保线程没有使用局部变量的引用或者指针。若一定要用,除非你能肯定该线程会在局部作用域结束前执行结束。使用join方式(阻塞)就不会出现这种问题,它会在作用域结束前完成退出。

1.4 std::async

      std::async是基于任务的,内部有调度器,比线程更高级别的抽象,可以看作是thread + packaged_task的封装。

原型 async(std::launch::async | std::launch::deferred, f, args...)
第一个参数 std::launch::deferred 延迟调用,延迟到future对象调用get()或者wait()的时候才执行线程函数f
std::launch::async 强制创建新线程,可能失败
std::launch::async |std::launch::deferred  默认值,系统会自行决定是异步(创建新线程)还是同步(不创建新线程)方式运行

       std::future与std::async配合使用。std::future提供了一个访问异步操作结果的机制,它和线程是一个级别的,属于低层次的对象。在它之上高一层的是std::packaged_task和std::promise,它们内部都有future以便访问异步操作结果。std::packaged_task包装的是一个异步操作(函数),std::promise包装的是一个值;都是为了方便异步操作的。

future_status三种状态
deferred 异步操作还没开始
ready 异步操作已经完成
timeout 异步操作超时
获取结果三种方式
get 等待异步操作结束并返回结果
wait 等待异步操作结束,没有返回值
wait_for 超时等待返回结果

代码示例:

#include 
#include 

using namespace std;

int main()
{
	future future1 = async(launch::async, []() { return 6; });
	cout << future1.get() << endl;  //output: 6

	future future2 = async(launch::async, []() {  cout << 8 << endl; });
	cout << "before wait" << endl;
	future2.wait(); //output: 8
	cout << "after wait" << endl;

	future future3 = async(launch::async, []() {
		this_thread::sleep_for(chrono::seconds(3));
	return 9;
		});

	cout << "waiting...\n";
	future_status status;
	do {
		//查询状态
		status = future3.wait_for(chrono::seconds(1));

		if (status == future_status::deferred) {
			cout << "deferred\n";
		}
		else if (status == future_status::timeout) {
			cout << "timeout\n";
		}
		else if (status == future_status::ready) {
			cout << "ready!\n";
		}
	} while (status != future_status::ready);

	cout << "result is " << future3.get() << endl;
}

运行结果:

C++11新特性(5):多线程_第4张图片

 before wait后面的数字8是future2输出的,还没来得及换行,说明确实是多线程在输出。

2. 线程同步

2.1 互斥锁

锁类型 作用
std::mutex 独占的互斥锁,不能递归使用
std::timed_mutex 带超时的独占互斥锁,不能递归使用
std::recursive_mutex 递归互斥锁,不带超时功能
std::recursive_timed_mutex 带超时的递归互斥锁

       基本用法如下,建议使用C++11新增的模板类lock_guard,可以简化互斥锁 lock()和unlock()的写法,同时也更安全。还有一个模板类是unique_lock,基本用法和lock_guard一样,提供了更多构造函数,但更占资源。

#include 
std::mutex _mutex;
_mutex.lock();//加锁,阻塞
_mutex.unlock();//解锁
_mutex.try_lock();//尝试加锁,成功返回bool,失败返回false不阻塞
或
std::lock_guard lock_mtx(_mutex);

2.2 条件变量

       条件变量需要和互斥量配合使用,属于另一种用于等待的同步机制,能阻塞一个或多个线程,直到收到另一个线程发出的通知或超时时,才能唤醒当前阻塞的线程。

使用
condition_variable 配合 std::unique_lock 进行 wait 操作,也就是阻塞线程的操作
conditon_variable_any 2.1里的四种锁

2.3 自旋锁

       自旋锁是一种忙等待形式的锁,会在用户态不停的询问锁是否可以获取(获取不到一直循环),不会陷入到内核态中,所以更加高效,但是可能会对CPU资源造成浪费。C++11中没有直接提供自旋锁的实现,但提供了原子操作的实现,可以借助原子操作实现简单的自旋锁。

       相比互斥锁,自旋锁效率更高,但是长时间的自旋可能会使CPU得不到充分的应用。在临界区代码较少,执行速度快的时候应该使用自旋锁。而互斥锁不会浪费CPU资源,在无法获得锁时使线程阻塞,将CPU让给其他线程使用。对于等待资源时间较长的场景,应该用互斥锁。

代码示例:

#include 
#include 
#include 

std::atomic_flag flag;
int a = 0;
void foo()
{
	for (int i = 0; i < 100; ++i)
	{
		while (flag.test_and_set())
		{

		}//加锁
		a += 1;
		flag.clear();//解锁
	}
}
int main()
{
	flag.clear();//初始化为clear状态

	std::thread t1(foo);
	std::thread t2(foo);
	t1.join();
	t2.join();
	std::cout << a << std::endl;
	return 0;
}

a. test_and_set:返回该atomic_flag对象当前状态,检查flag是否被设置,若被设置直接返回true,若没有设置则设置flag为true后再返回false。

b. clear:清除atomic_flag对象的标志位,即设置atomic_flag的值为false。

你可能感兴趣的:(C++,c++)