C++11:多线程

本文详细介绍了C++11中的线程创建、互斥量、条件变量、原子变量以及异步操作的相关概念和使用方法,包括线程的join和detach、互斥量的独占和递归类型、带超时功能的互斥量、条件变量的wait机制、原子变量的使用,以及call_once和future、promise、package_task在异步操作中的应用。文章强调了线程同步的重要性,以及如何通过标准库提供的工具来有效地管理和同步线程。
摘要由CSDN通过智能技术生成

1 线程

1.1 线程的创建

用std::thread创建线程非常简单,只需要提供线程函数或者函数对象即可,并且可以同时指定线程函数的参数。下面是创建线程的示例:

#include <thread>

void func ( )
{
	//do some work
}

int main ()
{
	std::thread t (func);
	t.join();
	return 0 ;
}

在上例中,函数func将会运行于线程对象t中,join 函数将会阻塞线程,直到线程函数执行结束,如果线程函数有返回值,返回值将被忽略。
如果不希望线程被阻塞执行,可以调用线程的detach()方法,将线程和线程对象分离。比如下面的例子:

#include <thread>

void func ( )
{
	//do some work
}

int main ()
{
	std::thread t (func);
	t.detach();
	//do other work
	return 0 ;
}

通过detach,线程就和线程对象分离了,让线程作为后台线程去执行,当前线程也不会阻塞了。但需要注意的是,detach之后就无法再和线程发生联系了,比如 detach 之后就不能再通过join来等待线程执行完,线程何时执行完我们也无法控制了。
线程还可以接收任意个数的参数:

void func (int i, double d,const std:: string& s)
{
	std::cout << i << ", "<< d << ", "<< s << std::endl;
}

int main ()
{
	std::thread t(func, 1, 2, "test");
	t.join();
	return 0;
}

上面的例子将会输出:1,2,test
使用这种方式创建线程很方便,但需要注意的是,std::thread出了作用域之后将会析构,这时如果线程函数还没有执行完则会发生错误,因此,需要保证线程函数的生命周期在线程变量std::thread的生命周期之内。
线程不能复制,但可以移动,例如:

#include <thread>

void func()
{
	//do some work
}

int main()
{
	std::thread t(func);
	std::thread t1(std::move(t));
	t.join();
	t1.join();
	return 0;
}

线程被移动之后,线程对象t将不再不代表任何线程了。另外,还可以通过std:bind或lambda表达式来创建线程,代码如下:

void func(int a, double b)
{
}

int main()
{
	std::thread t1(std::bind(func, 1, 2));
	std::thread t2([](int a,double b){}, 1, 2);
	t1.join();
	t2.join();
	return 0;
}

需要注意的是线程对象的生命周期,比如下面的代码:

#include <thread>

void func ()
{
	//do some work
}

int main ()
{
	std::thread t(func);
	return 0;
}

上面的代码运行可能会抛出异常,因为线程对象可能先于线程函数结束了,应该保证线程对象的生命周期在线程函数执行完时仍然存在。可以通过join方式来阻塞等待线程函数执行完,或者通过detach方式让线程在后台执行,还可以将线程对象保存到一个容器中,以保证线程对象的生命周期。比如下面的代码:

#include <thread>

std::vector<std::thread> g_list;
std::vector<std::shared_ptr<std::thread>> g_list2;
void createThread()
{
	std::thread t(func) ;
	g_list.push_back(std::move(t));
	g_list2.push_back(std::make_shared<std::thread>(func)) ;
}

int main ()
{
	CreateThread();
	for(auto& thread : g_list)
		thread.join () ;
	for (auto& thread : g_list2)
		thread->join ();
	return 0;
}

1.2 线程的基本用法

线程可以获取当前线程的ID,还可以获取CPU核心数量,例如:

void func()
{
}

int main()
{
	std::thread t(func) ;
	cout<<t.get_ id()<<endl;	//获取当前线程ID
	cout<<std::thread::hardware_concurrency()<<endl;	//获取CPU核数,如果获取失败则返回0
	return 0 ;
}

可以使当前线程休眠一定时间,代码如下:

void f()
{
	std::this_thread::sleep_for(std::chrono::seconds(3));
	cout<<"time out"<<endl;
}

int main ()
{
	std::thread t(f);
	t.join() ;
}

在上面的例子中,线程将会休眠3秒,3秒之后将打印time out。

2 互斥量

互斥量是一种同步原语,是一种线程同步的手段,用来保护多线程同时访问的共享数据。C++11中提供了如下4种语义的互斥量(mutex):
std::mutex:独占的互斥量,不能递归使用。
std::timed_mutex:带超时的独占互斥量,不能递归使用。
std::recursive_mutex:递归互斥量,不带超时功能。
std::recursive_timed_mutex:带超时的递归互斥量。

2.1 独占互斥量std::mutex

这些互斥量的基本接口很相似,一般用法是通过lock()方法来阻塞线程,直到获得互斥量的所有权为止。在线程获得互斥量并完成任务之后,就必须使用unlock()来解除对互斥量的占用,lock()和 unlock()必须成对出现。try_lock()尝试锁定互斥量,如果成功则返回 true,如果失败则返回false,它是非阻塞的。

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>

std::mutex g_lock;

void func()
{
	g_lock.lock();

	std::cout << "entered thread " << std::this_thread::get_id() << std::endl;
	std::this_thread::sleep_for(std::chrono::seconds(1));
	std::cout << "leaving thread " << std::this_thread::get_id() << std::endl;

	g_lock.unlock();
}

int main(void)
{
	std::thread t1(func);
	std::thread t2(func);
	std::thread t3(func);

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

	system("pause");
	return 0;
}

输出结果如下:
entered thread 10144
leaving thread 10144
entered thread 4188
leaving thread 4188
entered thread 3424
leaving thread 3424
使用lock _guard可以简化lock/unlock 的写法,同时也更安全,因为lock_guard 在构造时会自动锁定互斥量,而在退出作用域后进行析构时就会自动解锁,从而保证了互斥量的正确操作,避免忘记unlock操作,因此,应尽量用lock _guard。lock_guard用到了RAII技术,这种技术在类的构造函数中分配资源,在析构函数中释放资源,保证资源在出了作用域之后就释放。上面的例子使用lock _guard后会更简洁,代码如下:

void func ()
{
	std::lock_guard<std::mutex> locker(g_lock); 	//出作用域之后自动解锁
	std::cout << "entered thread " << std::this_thread::get_id() << std::endl;
	std::this_thread::sleep_for(std::chrono::seconds(1));
	std::cout << "leaving thread " << std::this_thread::get_id() << std::endl;
}

2.2 递归互斥量std::recursive_mutex

递归锁允许同一线程多次获得该互斥锁,可以用来解决同一线程需要多次获取互斥量时死锁的问题。一个线程多次获取同一个互斥量时会发生死锁:

#include <iostream>
#include <thread>
#include <mutex>

struct Complex
{
	std::mutex mutex;
	int i;

	Complex() : i(0){}

	void mul(int x)
	{
		std::lock_guard<std::mutex> lock(mutex);
		i *= x;
	}

	void div(int x)
	{
		std::lock_guard<std::mutex> lock(mutex);
		i /= x;
	}

	void both(int x, int y)
	{
		std::lock_guard<std::mutex> lock(mutex);
		mul(x);
		div(y);
	}
};

int main(void)
{
	Complex complex;

	complex.both(32, 23);

	system("pause");
	return 0;
}

这个例子°运行起来后就会发生死锁,因为在调用both时获取了互斥量,之后再调用mul又要获取相同的互斥量,但是这个互斥量已经被当前线程获取了,无法释放,这时就会发生死锁。要解决这个死锁的问题,一个简单的办法就是用递归锁: std::recursive_mutex,它允许同一线程多次获得互斥量:

#include <iostream>
#include <thread>
#include <mutex>

struct Complex
{
	std::recursive_mutex mutex;
	int i;

	Complex() : i(0){}

	void mul(int x)
	{
		std::lock_guard<std::recursive_mutex> lock(mutex);
		i *= x;
	}

	void div(int x)
	{
		std::lock_guard<std::recursive_mutex> lock(mutex);
		i /= x;
	}

	void both(int x, int y)
	{
		std::lock_guard<std::recursive_mutex> lock(mutex);
		mul(x);
		div(y);
	}
};

int main(void)
{
	Complex complex;

	complex.both(32, 23);  //因为同一线程可以多次获取同一互斥量,不会发生死锁

	system("pause");
	return 0;
}

需要注意的是尽量不要使用递归锁好,主要原因如下:
1)需要用到递归锁定的多线程互斥处理往往本身就是可以简化的,允许递归互斥很容易放纵复杂逻辑的产生,从而导致一些多线程同步引起的晦涩问题。
2)递归锁比起非递归锁,效率会低一些。
3)递归锁虽然允许同一个线程多次获得同一个互斥量,可重复获得的最大次数并未具体说明,一旦超过一定次数,再对lock进行调用就会抛出std::system 错误。

2.3 带超时的互斥量std::timed_mutex和std::recursive_timed_mutex

std::timed_mutex是超时的独占锁,std::recursive_timed_mutex是超时的递归锁,主要用在获取锁时增加超时等待功能,因为有时不知道获取锁需要多久,为了不至于一直在等待获取互斥量,就设置一个等待超时时间,在超时后还可以做其他的事情。
std::timed_mutex 比 std::mutex多了两个超时获取锁的接口:try_lock_for和 try_lock_until,这两个接口是用来设置获取互斥量的超时时间,使用时可以用一个while循环去不断地获取互斥量。

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>

std::timed_mutex mutex;

void work()
{
	std::chrono::milliseconds timeout(100);

	while (true)
	{
		if (mutex.try_lock_for(timeout))
		{
			std::cout << std::this_thread::get_id() << ": do work with the mutex" << std::endl;

			std::chrono::milliseconds sleepDuration(250);
			std::this_thread::sleep_for(sleepDuration);

			mutex.unlock();
			std::this_thread::sleep_for(sleepDuration);
		}
		else
		{
			std::cout << std::this_thread::get_id() << ": do work without the mutex" << std::endl;

			std::chrono::milliseconds sleepDuration(100);
			std::this_thread::sleep_for(sleepDuration);
		}
	}
}

int main(void)
{
	std::thread t1(work);
	std::thread t2(work);

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

	system("pause");
	return 0;
}

在上面的例子中,通过一个while循环不断地去获取超时锁,如果超时还没有获取到锁时就休眠100毫秒,再继续获取超时锁。
相比 std::timed_mutex,std::recursive_timed_mutex 多了递归锁的功能,允许同一线程多次获得互斥量。std::recursive_timed_mutex和 std::recursive_mutex的用法类似,可以看作在std::recursive_mutex的基础上加了超时功能。

3 条件变量

条件变量是C++11提供的另外一种用于等待的同步机制,它能阻塞一个或多个线程,直到收到另外一个线程发出的通知或者超时,才会唤醒当前阻塞的线程。条件变量需要和互斥量配合起来用。C++11提供了两种条件变量:
condition_variable,配合std::unique_lock<std::mutex>进行wait操作。
condition_variable_any,和任意带有lock、unlock语义的mutex搭配使用,比较灵活,但效率比 condition_variable差一些。
可以看到condition_variable_any 比 condition_variable更灵活,因为它更通用,对所有的锁都适用,而condition_variable性能更好。我们应该根据具体应用场景来选择条件变量。
条件变量的使用过程如下:
1)拥有条件变量的线程获取互斥量。
2)循环检查某个条件,如果条件不满足,则阻塞直到条件满足; 如果条件满足,则向下执行。
3)某个线程满足条件执行完之后调用notify_one或notify_all唤醒一个或者所有的等待线程。
可以用条件变量来实现一个同步队列,同步队列作为一个线程安全的数据共享区,经常用于线程之间数据读取,比如半同步半异步线程池的同步队列。

#include<list>
#include<mutex>
#include<thread>
#include<condition_variable>
#include <iostream>

template<typename T>
class SyncQueue
{
private:
	bool IsFull() const
	{
		return m_queue.size() == m_maxSize;
	}

	bool IsEmpty() const
	{
		return m_queue.empty();
	}

public:
	SyncQueue(int maxSize) : m_maxSize(maxSize)
	{
	}

	void Put(const T& x)
	{
		std::lock_guard<std::mutex> locker(m_mutex);

		while (IsFull())
		{
			std::cout << "缓冲区满了,需要等待..." << std::endl;
			m_notFull.wait(m_mutex);
		}

		m_queue.push_back(x);
		m_notFull.notify_one();
	}

	void Take(T& x)
	{
		std::lock_guard<std::mutex> locker(m_mutex);
		
		while (IsEmpty())
		{
			std::cout << "缓冲区空了,需要等待..." << std::endl;
			m_notEmpty.wait(m_mutex);
		}

		x = m_queue.front();
		m_queue.pop_front();
		m_notFull.notify_one();
	}

	bool Empty()
	{
		std::lock_guard<std::mutex> locker(m_mutex);
		return m_queue.empty();
	}

	bool Full()
	{
		std::lock_guard<std::mutex> locker(m_mutex);
		return m_queue.size() == m_maxSize;
	}

	size_t Size()
	{
		std::lock_guard<std::mutex> locker(m_mutex);
		return m_queue.size();
	}

	int Count()
	{
		return m_queue.size();
	}

private:
	std::list<T> m_queue;                  //缓冲区
	std::mutex m_mutex;                    //互斥量和条件变量结合起来使用
	std::condition_variable_any m_notEmpty;//不为空的条件变量
	std::condition_variable_any m_notFull; //没有满的条件变量
	int m_maxSize;                         //同步队列最大的size
};

这个同步队列在没有满的情况下可以插入数据,如果满了,则会调用m_notFull阻塞等待,待消费线程取出数据之后发一个未满的通知,然后前面阻塞的线程就会被唤醒继续往下执行;如果队列为空,就不能取数据,会调用m_notempty条件变量阻塞,等待插入数据的线程发出不为空的通知时,才能继续往下执行。以上过程是同步队列的工作过程。
std::lock_guard利用了RAII机制可以保证安全释放mutex。std::unique_lock 和 std::lock_guard 的差别在于前者可以自由地释放mutex,而后者则需要等到std::lock _guard变量生命周期结束时才能释放。条件变量的wait还有一个重载方法,可以接受一个条件。

std::lock_guard<std::mutex> locker(m_mutex);
while(IsFull())
{
	m_notFull.wait(m_mutex);
}

可以改为这样:

std::lock_guard<std::mutex> locker(m_mutex) ;
m_notFull.wait(locker, [this]{return !IsFull();});

两种写法效果是一样的,但是后者更简洁,条件变量会先检查判断式是否满足条件,如果满足条件,则重新获取 mutex,然后结束wait,继续往下执行; 如果不满足条件,则释放mutex,然后将线程置为waiting状态,继续等待。
这里需要注意的是,wait函数中会释放mutex,而lock_guard这时还拥有mutex,它只会在出了作用域之后才会释放mutex,所以,这时它并不会释放,但执行wait时会提前释放mutex。从语义上看这里使用lock_guard 会产生矛盾,但是实际上并不会出问题,因为 wait提前释放锁之后会处于等待状态,在被notify_one或者notify_all唤醒之后会先获取mutex, 这相当于lock_guard的 mutex在释放之后又获取到了,因此,在出了作用域之后lock_guard自动释放mutex不会有问题。这里应该用unique_lock,因为unique_lock 不像lock_guard一样只能在析构时才释放锁,它可以随时释放锁,因此,在wait 时让 unique_lock释放锁从语义上看更加准确。
我们可以把std::lock_guard改成std::unique_lock,把std::condition_variable_any改为std::condition_variable,并且用等待一个判断式的方法来实现一个简单的线程池。

#include <thread>
#include <condition_variable>
#include <mutex>
#include <list>
#include <iostream>

template<typename T>
class SimpleSyncQueue
{
public:
	SimpleSyncQueue(){}

	void Put(const T& x)
	{
		std::lock_guard<std::mutex> locker(m_mutex);
		m_queue.push_back(x);
		m_notEmpty.notify_one();
	}

	void Take(T& x)
	{
		std::unique_lock<std::mutex> locker(m_mutex);
		m_notEmpty.wait(locker, [this]{return !m_queue.empty(); });
		x = m_queue.front();
		m_queue.pop_front();
	}

	bool Empty()
	{
		std::lock_guard<std::mutex> locker(m_mutex);
		return m_queue.empty();
	}

	size_t Size()
	{
		std::lock_guard<std::mutex> locker(m_mutex);
		return m_queue.size();
	}

private:
	std::list<T> m_queue;
	std::mutex m_mutex;
	std::condition_variable m_notEmpty;
};

用unique_lock代替lock_guard使语义更加准确,用性能更好的condition_variable替代condition_variable_any,对程序加以优化,这里仍然用condition_variable_any也是可以的。执行wait时不再通过 while循环来判断,而是通过lambda表达式来判断,写法上更简洁了。

4 原子变量

C++11提供了一个原子类型std::atomic,可以使用任意类型作为模板参数,C++11内置了整型的原子变量,可以更方便地使用原子变量,使用原子变量就不需要使用互斥量来保护该变量了,用起来更简洁。例如,要做一个计数器,使用mutex时是这样的:

struct Counter
{
	int value;
	
	std::mutex mutex;
	
	void increment()
	{
		std::lock_guard<std::mutex> lock(mutex);
		++value;
	}
	
	void decrement ()
	{
		std::lock_guard<std::mutex> lock(mutex);
		--value;
	}
	
	int get()
	{
		return value
	}
};

如果使用原子变量,就不需要再定义互斥量了,使用更简便,如代码清单5-8所示。

#include <atomic>

struct AtomicCounter
{
	std::atomic<int> value;
	
	void increment() 
	{
		++value;
	}
	
	void decrement()
	{
		--value;
	}
	
	int get()
	{
		return value.load();
	}
}

5 call_once/once_flag 的使用

为了保证在多线程环境中某个函数仅被调用一次,比如,需要初始化某个对象,而这个对象只能初始化一次时,就可以用std::call_once来保证函数在多线程环境中只被调用一次。使用std::call_once时,需要一个once_flag 作为call_once的入参,它的用法比较简单。

#include <iostream>
#include <thread>
#include <mutex>

std:: once_flag flag;

void do_once()
{
	std::call_once(flag, [](){ std::cout << "called once" << std::endl; });
}

int main()
{
	std::thread t1(do_once);
	std::thread t2(do_once);
	std::thread t3(do_once);
	t1.join();
	t2.join();
	t3.join();
}

输出结果如下:
called once

6 异步操作

C++11提供了异步操作相关的类,主要有std::future、std::promise和 std::package_task。std::future作为异步结果的传输通道,可以很方便地获取线程函数的返回值; std::promise用来包装一个值,将数据和 future绑定起来,方便线程赋值; std::package_task用来包装一个可调用对象,将函数和 future绑定起来,以便异步调用。

6.1获取线程函数返回值的类std::future

C++11中增加的线程,使得我们可以非常方便地创建和使用线程,但有时会有些不便,比如希望获取线程函数的返回结果,就不能直接通过thread.join()得到结果,这时就必须定义一个变量,在线程函数中去给这个变量赋值,然后执行join(),最后得到结果,这个过程是比较烦琐的。thread 库提供了future用来访问异步操作的结果,因为一个异步操作的结果不能马上获取,只能在未来某个时候从某个地方获取,这个异步操作的结果是一个未来的期待值,所以被称为future,future提供了获取异步操作结果的通道。我们可以以同步等待的方式来获取结果,可以通过查询future的状态(future_status)来获取异步操作的结果。future_status有如下3种状态:
(1)Deferred,异步操作还没开始。
(2)Ready,异步操作已经完成。
(3)Timeout,异步操作超时。
我们可以查询future的状态,通过它内部的状态可以知道异步任务的执行情况,比如下面的代码将不断查询future的状态,直到任务完成为止。

//查询future的状态
std::future_status status;
do {
	status = future.wait_for(std::chrono::seconds(1));
	if(status == std::future_status::deferred)
		std::cout << "deferred \n";
	else if(status == std::future_status::timeout)
		std::cout << "timeout \n";
	else if(status == std::future_status::ready)
		std::cout << "ready! \n";
}while(status != std::future_status::ready);

获取future结果有3种方式: get, wait, wait_for,其中 get等待异步操作结束并返回结果,wait只是等待异步操作完成,没有返回值,wait_for是超时等待返回结果。

6.2 协助线程赋值的类std::promise

std::promise将数据和 future绑定起来,为获取线程函数中的某个值提供便利,在线程函数中为外面传进来的promise赋值,在线程函数执行完成之后就可以通过promise的future获取该值了。值得注意的是,取值是间接地通过promise内部提供的 future来获取的。std::promise的基本用法如下:

std::promise<int> pr;
std::thread t([](std::promise<int>&p){p.set_value_at_thread_exit(9);}, std::ref(pr));
std::future<int> f = pr.get_future();
auto r = f.get() ;

6.3 可调用对象的包装类std::package_task

std::packaged_task包装了一个可调用对象的包装类(如function、lambda expression ,bind expression和 another function object),将函数和future绑定起来,以便异步调用,它和std:promise在某种程度上有点像,promise保存了一个共享状态的值,而packaged_task保存的是一个函数。std::promise的基本用法如下:

std::packaged_task<int()> task([](){ return 7; );
std::thread t1(std::ref(task)) ;
std::future<int> f1 = task.get_future();
auto r1 = f1.get ();

6.4 std::promise、std:packaged_task和std::future三者之间的关系

std::future提供了一个访问异步操作结果的机制,它和线程是一个级别的,属于低层次的对象。在 std::future之上的高一层是std::packaged_task 和std::promise,它们内部都有 future 以便访问异步操作结果,std::packaged_task包装的是一个异步操作,而 std::promise包装的是一个值,都是为了方便异步操作,因为有时需要获取线程中的某个值,这时就用std::promise, 而有时需要获一个异步操作的返回值,这时就用std::packaged_task。那么std::promise和std::packaged_task之间又是什么关系呢?可以将一个异步操作的结果保存到std::promise中。
future被promise和package_task用来作为异步操作或者异步结果的连接通道,用std::future和std::shared_future来获取异步调用的结果。future是不可拷贝的,只能移动,shared_future是可以拷贝的,当需要将future放到容器中则需要用shared_future。

#include <iostream>
#include <thread>
#include <utility>
#include <future>
#include <vector>

int func(int x) { return x + 2; }

int main(void)
{
	std::packaged_task<int(int)> tsk(func);
	std::future<int> fut = tsk.get_future();  //获取future

	std::thread(std::move(tsk), 2).detach();

	int value = fut.get();  //等待task完成并获取返回值
	std::cout << "The result is " << value << ".\n";

	std::vector<std::shared_future<int>> v;
	std::shared_future<int> f = std::async(std::launch::async, [](int a, int b){return a + b; }, 2, 3);

	v.push_back(f);
	std::cout << "The shared_future result is " << v[0].get() << std::endl;

	system("pause");
	return 0;
}

输出结果如下:
The result is 4
The shared_future result is 5

7 线程异步操作函数async

std::async 比 std::promise、std::packaged_task和std::thread更高一层,它可以用来直接创建异步的task,异步任务返回的结果也保存在future中,当需要获取异步任务的结果时,只需要调用future.get()方法即可,如果不关注异步任务的结果,只是简单地等待任务完成的话,则调用future.wait()方法。
现在看一下std::async的原型async(std::launch::async | std::launch::deferred, f, args…),第一个参数是线程的创建策略,有两种策略,默认的策略是立即创建线程。
std::launch::async在调用async时就开始创建线程。
std::launch::deferred以延迟加载方式创建线程。调用async时不创建线程,直到调用了future的get或者wait 时才创建线程。
第二个参数是线程函数, 第三个参数是线程函数的参数。

#include <iostream>
#include <future>

void TestAsync()
{
	std::future<int> f1 = std::async(std::launch::async, [](){return 8;});

	std::cout << f1.get() << std::endl; //output: 8

	std::future<void> f2 = std::async(std::launch::async, [](){std::cout << 8 << std::endl; return;});

	f2.wait(); //output: 8

	std::future<int> future = std::async(std::launch::async, [](){
		std::this_thread::sleep_for(std::chrono::seconds(3));
		return 8;
	});

	std::cout << "waiting...\n";
	std::future_status status;

	do {
		status = future.wait_for(std::chrono::seconds(1));

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

	std::cout << "result is " << future.get() << '\n';
}

int main(void)
{
	TestAsync();

	system("pause");
	return 0;
}

可能的结果如下:
waiting. …
timeouttimeoutready !
result is 8
std::async是更高层次的异步操作,使得我们不用关注线程创建内部细节,就能方便地获取异步执行状态和结果,还可以指定线程创建策略: 应该用std::async替代线程的创建,让它成为我们做异步操作的首选。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

给算法爸爸上香

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值