《c++ concurrency in action》读书笔记4--线程间同步

线程间同步也是十分常见的场景,可以选择的方法:

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()函数。

支持时间限制的函数:

 

  

  

 

  

  

 

  

posted @ 2019-06-12 14:48  雨异奇  阅读(217)  评论(0编辑  收藏  举报