线程间同步也是十分常见的场景,可以选择的方法:
1. 一直检查变量的值,看任务是否完成。但这个方法有很多弊端(线程需要占用cpu的处理时间,而且检查变量需要拿互斥锁)
2. 周期检查变量的值(sleep 一段时间)
bool flag;
std::mutex m;
void wait_for_flag()
{
std::unique_lock<std::mutex> lk(m);
while(!flag)
{
lk.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
lk.lock();
}
}
弊端:a. sleep时间的设置,
3. c++标准提供了条件变量和futures来处理这种情况。
一、条件变量
1. c++标准库定义了std::condition_variable 和 std::condition_variable_any两个条件变量,std::condition_variable只能和std::mutex一起工作,而 std::condition_variable_any可以和满足互斥最低要求的变量一起工作,所以condition_variable_any更通用,但condition_variable的性能更好,应该优先选用condition_variable。
std::mutex mut;
std::queue<data_chunk> data_queue;
std::condition_variable data_cond;
void data_preparation_thread()
{
while(more_data_to_prepare())
{
data_chunk const data=prepare_data();
std::lock_guard<std::mutex> lk(mut);
data_queue.push(data);
data_cond.notify_one();
}
}
void data_processing_thread()
{
while(true)
{
std::unique_lock<std::mutex> lk(mut);
data_cond.wait(
lk,[]{return !data_queue.empty();});
data_chunk data=data_queue.front();
data_queue.pop();
lk.unlock();
process(data);
if(is_last_chunk(data))
break;
}
}
data_preparation_thread中拿锁将数据放入队列中,并通知条件变量。data_processing_thread中data_cond的wait函数会同过拿锁检查第二个参数得结果,如果为true,则继续执行。如果为false放锁,并阻塞,直到其他线程调用notify.
二、future
c++标准库利用future来处理一次性事件(比如登机), c++中定义了两种类型的future, unique futures(std::future<>) 和 shared futures (std::shared_future<>)。 一个std::futures的实例是引用关联事件的唯一实例,而多个std::shared_future的实例可以对应到同一个事件。
需要注意,如果多个线程访问同一个future对象,需要加互斥锁(或类似的机制),而多线程利用自己std::shared_funture<>的拷贝访问数据则不需要同步。
1. std::async和future
std::async可以直接返回一个future对象。
#include <future>
#include <iostream>
int find_the_answer_to_ltuae();
void do_other_stuff();
int main()
{
std::future<int> the_answer=std::async(find_the_answer_to_ltuae);
do_other_stuff();
std::cout<<"The answer is "<<the_answer.get()<<std::endl;
}
std::async启动一个异步的task,并返回一个future对象,通过调用future对象的get()函数可以得到异步task的运行结果。如果调用get()函数时future不是ready状态,则当前调用线程会被block住,直到future的状态变成ready为止。
std::async可以通过第一个参数决定其运行方式:std::launch::async 在一个新线程中运行, std::launch::deferred推迟到调用wait()或get()时执行。
// Run in new thread
auto f6=std::async(std::launch::async,Y(),1.2);
// Run in wait() or get()
auto f7= std::async(std::launch::deferred,baz,std::ref(x));
// Implementation chooses
auto f8=std::async(
std::launch::deferred | std::launch::async, baz,std::ref(x));
auto f9=std::async(baz,std::ref(x));
f7.wait(); // Invoke deferred function
2.std::packaged_task 和 future
packaged_task提供了一种方法将future和函数或callback对象关联起来,当std::packaged_task<>的对象被唤醒,就会调用关联的函数或callback,并将future的状态置为ready。
#include <deque>
#include <mutex>
#include <future>
#include <thread>
#include <utility>
std::mutex m;
std::deque<std::packaged_task<void()> > tasks;
bool gui_shutdown_message_received();
void get_and_process_gui_message();
void gui_thread()
{
while(!gui_shutdown_message_received())
{
get_and_process_gui_message();
std::packaged_task<void()> task;
{
std::lock_guard<std::mutex> lk(m);
if(tasks.empty())
continue;
task=std::move(tasks.front());
tasks.pop_front();
}
task();
}
}
std::thread gui_bg_thread(gui_thread);
template<typename Func>
std::future<void> post_task_for_gui_thread(Func f)
{
std::packaged_task<void()> task(f);
std::future<void> res=task.get_future();
std::lock_guard<std::mutex> lk(m);
tasks.push_back(std::move(task));
return res;
}
3. std::promise和future
std::promise<>提供了一种方法设置一个值,并可以通过相关连的future进行读取。当std::promise<>调用set_value()后,对应的future会变成ready状态。 如果std::promise<>没有设置值,就本析构了,则异常值会被存储在future中。
#include <future>
void process_connections(connection_set& connections)
{
while(!done(connections))
{
for(connection_iterator
connection=connections.begin(),end=connections.end();
connection!=end;
++connection)
{
if(connection->has_incoming_data())
{
data_packet data=connection->incoming();
std::promise<payload_type>& p= connection->get_promise(data.id);
p.set_value(data.payload);
}
if(connection->has_outgoing_data())
{
outgoing_packet data= connection->top_of_outgoing_queue();
connection->send(data.payload);
data.promise.set_value(true);
}
}
}
}
4 future和异常
double square_root(double x)
{
if(x<0)
{
throw std::out_of_range(“x<0”);
}
return sqrt(x);
}
std::future<double> f=std::async(square_root,-1);
double y=f.get();
当std::async、std::packaged_task抛出异常后,异常会被存储到future中,future的状态会变成ready, 当调用get()时,异常会被重新抛出来。
当使用std::promise,想要存储exception时,则调用set_exception()而不是set_value()。
extern std::promise<double> some_promise;
try
{
some_promise.set_value(calculate_value());
}
catch(...)
{
some_promise.set_exception(std::current_exception());
// 或者
// some_promise.set_exception(std::copy_exception(std::logic_error("foo ")));
}
如果promise没有调set函数或者packaged_task没有被唤醒,对象就被销毁的话,future会被设置为ready状态,exception被存在future中。
5. std::shared_future
对于std::future来说,这有一个线程能获取数据,调用一次get()后,则没有数据留下。如果想在多线程中等待同一个事件的结果,应该使用std::shard_future.
需要注意的是,如果想在多线程中利用同一个std::shard_futured对象获取数据,也会有数据竞争问题,需要用锁保护。更优的方法是,每个线程拷贝一个std::shard_futured对象的备份,然后利用自身拷贝获取数据。
利用std::future构造std::shared_future
std::promise<int> p;
std::future<int> f(p.get_future());
assert(f.valid());
std::shared_future<int> sf(std::move(f));
assert(!f.valid());
assert(sf.valid());
std::future直接转为std::shared_future
std::promise< std::map< SomeIndexType, SomeDataType, SomeComparator,
SomeAllocator>::iterator> p;
auto sf=p.get_future().share();
三、 有时间限制的等待
有两种timeout的时间,持续时间(比如等待30ms) 和绝对时间(比如等待到17:30:15 utc), 处理持续时间的变体带有_for后缀, 处理绝对时间的变体带有_until后缀。
所以对于std::condition_variable来说,会有对应的wait_for, wait_until()函数。
支持时间限制的函数: