C++并发编程(CH04)[future-02]

Waiting for one-off events with futures

  1. std::shared_future和std::future 的行为和
    std::shared_ptr和std::unique_ptr类似.
  2. future一旦变成ready状态.就不能reset状态了.
  3. 这两个future都是模板.模板参数就是关联数据.
  4. std::future<void> 表示没有关联数据.
  5. 如果很多个线程需要访问同一个future你需要上锁来访问.但是
    shared_future 就不需要加锁,多个线程都可以访问.
  6. 还有一个命名空间中也有两个future. std::experimental
    这个空间的代码质量也很高,只是它如果被纳入std.代码形式会不一样.你需要警惕,代码未来的不一致行为和维护成本.

Returning values from background tasks

#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;
  //(sai:如何使用future对象,延迟获取结果)
}

std::async

#include <string>
#include <future>
struct X
{
  void foo(int,std::string const&);
  std::string bar(std::string const&);
};
X x;
//<--Calls p->foo(42,"hello") where p is &x  注意这里是引用x
auto f1=std::async(&X::foo,&x,42,"hello"); 
//<--Calls tmpx.bar("goodbye") where tmpx is a copy of x 注意这里是拷贝x
auto f2=std::async(&X::bar,x,"goodbye");   
struct Y
{
  double operator()(double);
};
Y y;
auto f3=std::async(Y(),3.141);             //<--Calls tmpy(3.141) where tmpy is move-constructed from Y()
auto f4=std::async(std::ref(y),2.718);     //<--Calls y(2.718)
X baz(X&);
std::async(baz,std::ref(x));               //<--Calls baz(x)
class move_only
{
 public:
  move_only();
  move_only(move_only&&)
  move_only(move_only const&) = delete;
  move_only& operator=(move_only&&);
  move_only& operator=(move_only const&) = delete;
  void operator()();
};
auto f5=std::async(move_only());           //<--Calls tmp() where tmp is constructed from std::move(move_only())

参数调用各种调用方法,
你可以制定是wait的时候延迟启动函数.还是即时启动线程.默认参数是让具体C++实现自己去选择.

auto f6=std::async(std::launch::async,Y(),1.2);                //<--Run in new thread
auto f7=std::async(std::launch::deferred,baz,std::ref(x));     //<--Run in wait() or get()
auto f8=std::async(                                            //<--Implementation chooses
                   std::launch::deferred | std::launch::async, //<--Implementation chooses
                   baz,std::ref(x));                           //<--Implementation chooses
auto f9=std::async(baz,std::ref(x));                           //<--Implementation chooses
f7.wait();                                                     //<--Invoke deferred function
//(sai:如何按照各种需求启动异步线程)
//(sai:注意std::async是一个模板函数,而promise和packaged_task是模板类,都是为了实现异步线程)

Associating a task with a future

穿件task对象,里面带有一个future.我们不用手动写future返回值.这会更方便.这就是
packaged_task , 它的函数原型为.

template<>
class packaged_task<std::string(std::vector<char>*,int)>
{
 public:

  template<typename Callable>
  explicit packaged_task(Callable&& f);
  std::future<std::string> get_future();
  void operator()(std::vector<char>*,int);
};

packaged_task 是一个模板,参数是函数签名(函数类型).

PASSING TASKS BETWEEN THREADS

#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()                          //<--1
{
  while(!gui_shutdown_message_received())  //<--2
    {
      get_and_process_gui_message();       //<--3
      std::packaged_task<void()> task;
      {
        std::lock_guard<std::mutex> lk(m);
        if(tasks.empty())                  //<--4
          continue;
        task=std::move(tasks.front());     //<--5
        tasks.pop_front();
      }
      task();                              //调用task对象的call operator.
    }
}
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);      //<--7
  std::future<void> res=task.get_future(); //<--8
  std::lock_guard<std::mutex> lk(m);
  tasks.push_back(std::move(task));        //<--9
  return res;                              //<--10
}

GUI多线程处理方式,使用packaged_task和future

std::packaged_task<void()>是无参数无返回值函数的意思,即便有返回值函数,也会不忽略丢弃.

Making (std::)promises

  1. 由此可见package_task是跑一个函数线程并存储数据到它里面引用的future对象.
    future对象是movable but not copyable的.
  2. 你如何不启动 package_task 和 async 来创建future并设置数据返回.
    你因为可能需要用一个线程来处理多个链接,然后来处理数据返回给用户.因为
    package_task , async 只能和一个future相关联. 这时候你需要用
    promises
#include <future>
void process_connections(connection_set& connections)
{
    while(!done(connections))                       //<--1
    {
        for(connection_iterator connection=connections.begin(),end=connections.end(); connection!=end; ++connection)
        {
            if(connection->has_incoming_data())     //<--3
            {
                data_packet data=connection->incoming();
                std::promise<payload_type>& p = connection->get_promise(data.id); //<--4
                p.set_value(data.payload);
            }
            if(connection->has_outgoing_data())     //<--5
            {
                outgoing_packet data = connection->top_of_outgoing_queue();
                connection->send(data.payload);
                data.promise.set_value(true);       //<--6
            }
        }
    }
}

Saving an exception for the future

如果在线程中出现异常如何通过future传递异常.

double square_root(double x)
{
  if(x<0)
    {

      throw std::out_of_range("x<0");
    }
  return sqrt(x);
}

如果下面这么调用就会发生异常

double y=square_root(-1);

你在获取future的时候会怎么样?

std::future<double> f=std::async(square_root,-1);
double y=f.get();

如果异步线程中抛出一个异常,那么使用future的get的时候会触发异常再次抛出。但是C++不保证再次抛出的异常是不是原异常本身还是拷贝。不同的编译器有不同的实现。同样的道理也包含
package_task.另外promises也含有这样的机制.只是触发行为不一样.

std::promise 抛出异常需要手动条用一次set_exception()

extern std::promise<double> some_promise;
try
  {
    some_promise.set_value(calculate_value());
  }
 catch(...)
   {
     some_promise.set_exception(std::current_exception());
   }

可以直接抛出异常,也可以直接拷贝一个异常并不在当时抛出。留给future的get时抛出异常.

some_promise.set_exception(std::copy_exception(std::logic_error("foo ")));

应该优先使用这种异常抛出方式,因为编译器可以进行优化

  1. 如果在set future之前摧毁promise或者packaged
    task那么会触发异常。这是为了不让等待线程一直等待下去
  2. future只能允许一个线程等待,如果要允许多个线程等待。请使用shared_future

Waiting from multiple threads

  1. future get 之后其他进程访问就拿不到数据了。会引发异常.但是
    shared_ptr可以调用get多次.
  2. std::shared_future
    是copyable的.但是访问同一个变量还是会带来竞争问题.但是你每一个线程拿一份
    shared_future 的拷贝就不会出现这个问题了.
std::promise<int> p;
std::future<int> f(p.get_future()); 
assert(f.valid());  //<-- 1 the future f is valid
std::shared_future<int> sf(std::move(f)); 
assert(!f.valid()); //<-- 2 f is no longer valid
assert(sf.valid()); //<-- 3 sf is now valid
//注意,如果要用一个共享future引用住一个future对象请使用move,其他方式时非法的。因为future时not copyable的对象

你可以这样来创建 shared_future

std::promise<std::string> p;//<--1 Implicit transfer of ownership
std::shared_future<std::string> sf(p.get_future());
//右值,可以隐式转换.

你还可以这样创建当然这需要C++14的支持.

std::promise< std::map< SomeIndexType, SomeDataType, SomeComparator,
                        SomeAllocator>::iterator> p;
auto sf=p.get_future().share();
//(future含有一个share成员函数,可以很方便的创建share_ptr)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值