【C/C++开发】C++11 并发指南三(std::mutex 详解)

本系列文章主要介绍 C++11 并发编程,计划分为 9 章介绍 C++11 的并发和多线程编程,分别以下:css

C++11 并发指南一(C++11 多线程初探)(本章计划 1-2 篇,已完成 1 篇)html

C++11 并发指南二(std::thread 详解)(本章计划 1-2 篇,已完成 1 篇)ios

C++11 并发指南三(std::mutex 详解)(本章计划 1-2 篇,已完成 2 篇)编程

  1. C++11 并发指南三(std::mutex 详解)
  2. C++11 并发指南三(Lock 详解)

C++11 并发指南四(future 详解)(本章计划 3 篇,已完成 3 篇)promise

  1. C++11 并发指南四(<future> 详解一 std::promise 介绍)
  2. C++11 并发指南四(<future> 详解二 std::packaged_task 介绍)
  3. C++11 并发指南四(<future> 详解三 std::future & std::shared_future)

C++11 并发指南五(std::condition_variable 详解)(本章计划 1 篇,已完成 1 篇)多线程

C++11 并发指南六(atomic 详解)(本章计划 4 篇,已完成 4 篇)并发

  1. C++11 并发指南六(atomic 类型详解一 atomic_flag 介绍)
  2. C++11 并发指南六( <atomic> 类型详解二 std::atomic )
  3. C++11 并发指南六(atomic 类型详解三 std::atomic (续))
  4. C++11 并发指南六(atomic 类型详解四 C 风格原子操做介绍)

C++11 并发指南七(C++11 内存模型)(本章计划 3-4 篇,已完成 1 篇,3 篇在草稿中)异步

  1. C++11 并发指南七(C++11 内存模型一:介绍)

C++11 并发指南八(杂项)(本章计划 1-2 篇,已完成 0 篇,1 篇在草稿中)async

C++11 并发指南九(综合运用: C++11 多线程下生产者消费者模型详解)(本章计划 1-2 篇,已完成 1 篇)ide

按照目前的进度来看大约完成了整体进度的 60% 左右,但愿对你们理解和掌握 C++11 的并发和多线程编程有必定帮助。



上一篇《C++11 并发指南二(std::thread 详解)》中主要讲到了 std::thread 的一些用法,并给出了两个小例子,本文将介绍 std::mutex 的用法。

Mutex 又称互斥量,C++ 11中与 Mutex 相关的类(包括锁类型)和函数都声明在 <mutex> 头文件中,因此若是你须要使用 std::mutex,就必须包含 <mutex> 头文件。

<mutex> 头文件介绍

Mutex 系列类(四种)

  • std::mutex,最基本的 Mutex 类。
  • std::recursive_mutex,递归 Mutex 类。
  • std::time_mutex,定时 Mutex 类。
  • std::recursive_timed_mutex,定时递归 Mutex 类。

Lock 类(两种)

  • std::lock_guard,与 Mutex RAII 相关,方便线程对互斥量上锁。
  • std::unique_lock,与 Mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。

其余类型

  • std::once_flag
  • std::adopt_lock_t
  • std::defer_lock_t
  • std::try_to_lock_t

函数

  • std::try_lock,尝试同时对多个互斥量上锁。
  • std::lock,能够同时对多个互斥量上锁。
  • std::call_once,若是多个线程须要同时调用某个函数,call_once 能够保证多个线程对该函数只调用一次。

std::mutex 介绍

下面以 std::mutex 为例介绍 C++11 中的互斥量用法。

std::mutex 是C++11 中最基本的互斥量,std::mutex 对象提供了独占全部权的特性——即不支持递归地对 std::mutex 对象上锁,而 std::recursive_lock 则能够递归地对互斥量对象上锁。

std::mutex 的成员函数

  • 构造函数,std::mutex不容许拷贝构造,也不容许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的。
  • lock(),调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种状况:(1). 若是该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock以前,该线程一直拥有该锁。(2). 若是当前互斥量被其余线程锁住,则当前的调用线程被阻塞住。(3). 若是当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。
  • unlock(), 解锁,释放对互斥量的全部权。
  • try_lock(),尝试锁住互斥量,若是互斥量被其余线程占有,则当前线程也不会被阻塞。线程调用该函数也会出现下面 3 种状况,(1). 若是当前互斥量没有被其余线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量。(2). 若是当前互斥量被其余线程锁住,则当前调用线程返回 false,而并不会被阻塞掉。(3). 若是当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。

下面给出一个与 std::mutex 的小例子(参考

复制代码
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex

volatile int counter(0); // non-atomic counter
std::mutex mtx;           // locks access to counter

void attempt_10k_increases() {
    for (int i=0; i<10000; ++i) {
        if (mtx.try_lock()) {   // only increase if currently not locked:
            ++counter;
            mtx.unlock();
        }
    }
}

int main (int argc, const char* argv[]) {
    std::thread threads[10];
    for (int i=0; i<10; ++i)
        threads[i] = std::thread(attempt_10k_increases);

    for (auto& th : threads) th.join();
    std::cout << counter << " successful increases of the counter.\n";

    return 0;
}
复制代码

std::recursive_mutex 介绍

std::recursive_mutex 与 std::mutex 同样,也是一种能够被上锁的对象,可是和 std::mutex 不一样的是,std::recursive_mutex 容许同一个线程对互斥量屡次上锁(即递归上锁),来得到对互斥量对象的多层全部权,std::recursive_mutex 释放互斥量时须要调用与该锁层次深度相同次数的 unlock(),可理解为 lock() 次数和 unlock() 次数相同,除此以外,std::recursive_mutex 的特性和 std::mutex 大体相同。

std::time_mutex 介绍

std::time_mutex 比 std::mutex 多了两个成员函数,try_lock_for(),try_lock_until()。

try_lock_for 函数接受一个时间范围,表示在这一段时间范围以内线程若是没有得到锁则被阻塞住(与 std::mutex 的 try_lock() 不一样,try_lock 若是被调用时没有得到锁则直接返回 false),若是在此期间其余线程释放了锁,则该线程能够得到对互斥量的锁,若是超时(即在指定时间内仍是没有得到锁),则返回 false。

try_lock_until 函数则接受一个时间点做为参数,在指定时间点未到来以前线程若是没有得到锁则被阻塞住,若是在此期间其余线程释放了锁,则该线程能够得到对互斥量的锁,若是超时(即在指定时间内仍是没有得到锁),则返回 false。

下面的小例子说明了 std::time_mutex 的用法(参考)。

复制代码
#include <iostream>       // std::cout
#include <chrono>         // std::chrono::milliseconds
#include <thread>         // std::thread
#include <mutex>          // std::timed_mutex

std::timed_mutex mtx;

void fireworks() {
  // waiting to get a lock: each thread prints "-" every 200ms:
  while (!mtx.try_lock_for(std::chrono::milliseconds(200))) {
    std::cout << "-";
  }
  // got a lock! - wait for 1s, then this thread prints "*"
  std::this_thread::sleep_for(std::chrono::milliseconds(1000));
  std::cout << "*\n";
  mtx.unlock();
}

int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(fireworks);

  for (auto& th : threads) th.join();

  return 0;
}
复制代码

std::recursive_timed_mutex 介绍

和 std:recursive_mutex 与 std::mutex 的关系同样,std::recursive_timed_mutex 的特性也能够从 std::timed_mutex 推导出来,感兴趣的同鞋能够自行查阅。 ;-)

std::lock_guard 介绍

与 Mutex RAII 相关,方便线程对互斥量上锁。例子(参考):

复制代码
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::lock_guard
#include <stdexcept>      // std::logic_error

std::mutex mtx;

void print_even (int x) {
    if (x%2==0) std::cout << x << " is even\n";
    else throw (std::logic_error("not even"));
}

void print_thread_id (int id) {
    try {
        // using a local lock_guard to lock mtx guarantees unlocking on destruction / exception:
        std::lock_guard<std::mutex> lck (mtx);
        print_even(id);
    }
    catch (std::logic_error&) {
        std::cout << "[exception caught]\n";
    }
}

int main ()
{
    std::thread threads[10];
    // spawn 10 threads:
    for (int i=0; i<10; ++i)
        threads[i] = std::thread(print_thread_id,i+1);

    for (auto& th : threads) th.join();

    return 0;
}
复制代码

std::unique_lock 介绍

与 Mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。例子(参考):

复制代码
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock

std::mutex mtx;           // mutex for critical section

void print_block (int n, char c) {
    // critical section (exclusive access to std::cout signaled by lifetime of lck):
    std::unique_lock<std::mutex> lck (mtx);
    for (int i=0; i<n; ++i) {
        std::cout << c;
    }
    std::cout << '\n';
}

int main ()
{
    std::thread th1 (print_block,50,'*');
    std::thread th2 (print_block,50,'$');

    th1.join();
    th2.join();

    return 0;
}
复制代码

好了,本文暂时讲到这里,还剩下 std::try_lock,std::lock,std::call_once 三个函数没有讲到,留在下一篇博客中讲吧 ;-)

前面两讲《C++11 并发指南二(std::thread 详解)》,《C++11 并发指南三(std::mutex 详解)》分别介绍了 std::thread 和 std::mutex,相信读者对 C++11 中的多线程编程有了一个最基本的认识,本文将介绍 C++11 标准中 <future> 头文件里面的类和相关函数。

<future> 头文件中包含了如下几个类和函数:

  • Providers 类:std::promise, std::package_task
  • Futures 类:std::future, shared_future.
  • Providers 函数:std::async()
  • 其余类型:std::future_error, std::future_errc, std::future_status, std::launch.

std::promise 类介绍

promise 对象能够保存某一类型 T 的值,该值可被 future 对象读取(可能在另一个线程中),所以 promise 也提供了一种线程同步的手段。在 promise 对象构造时能够和一个共享状态(一般是std::future)相关联,并能够在相关联的共享状态(std::future)上保存一个类型为 T 的值。

能够经过 get_future 来获取与该 promise 对象相关联的 future 对象,调用该函数以后,两个对象共享相同的共享状态(shared state)

  • promise 对象是异步 Provider,它能够在某一时刻设置共享状态的值。
  • future 对象能够异步返回共享状态的值,或者在必要的状况下阻塞调用者并等待共享状态标志变为 ready,而后才能获取共享状态的值。

下面以一个简单的例子来讲明上述关系

复制代码
#include <iostream>       // std::cout
#include <functional>     // std::ref
#include <thread>         // std::thread
#include <future>         // std::promise, std::future

void print_int(std::future<int>& fut) {
    int x = fut.get(); // 获取共享状态的值.
    std::cout << "value: " << x << '\n'; // 打印 value: 10.
}

int main ()
{
    std::promise<int> prom; // 生成一个 std::promise<int> 对象.
    std::future<int> fut = prom.get_future(); // 和 future 关联.
    std::thread t(print_int, std::ref(fut)); // 将 future 交给另一个线程t.
    prom.set_value(10); // 设置共享状态的值, 此处和线程t保持同步.
    t.join();
    return 0;
}
复制代码

std::promise 构造函数

default (1)
promise();
with allocator (2)
template <class Alloc> promise (allocator_arg_t aa, const Alloc& alloc);
copy [deleted] (3)
promise (const promise&) = delete;
move (4)
promise (promise&& x) noexcept;
  1. 默认构造函数,初始化一个空的共享状态。
  2. 带自定义内存分配器的构造函数,与默认构造函数相似,可是使用自定义分配器来分配共享状态。
  3. 拷贝构造函数,被禁用。
  4. 移动构造函数。

另外,std::promise 的 operator= 没有拷贝语义,即 std::promise 普通的赋值操做被禁用,operator= 只有 move 语义,因此 std::promise 对象是禁止拷贝的。

例子:

复制代码
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <future>         // std::promise, std::future

std::promise<int> prom;

void print_global_promise () {
    std::future<int> fut = prom.get_future();
    int x = fut.get();
    std::cout << "value: " << x << '\n';
}

int main ()
{
    std::thread th1(print_global_promise);
    prom.set_value(10);
    th1.join();

    prom = std::promise<int>();    // prom 被move赋值为一个新的 promise 对象.

    std::thread th2 (print_global_promise);
    prom.set_value (20);
    th2.join();

  return 0;
}
复制代码

 std::promise::get_future 介绍

该函数返回一个与 promise 共享状态相关联的 future 返回的 future 对象能够访问由 promise 对象设置在共享状态上的值或者某个异常对象。只能从 promise 共享状态获取一个 future 对象。在调用该函数以后,promise 对象一般会在某个时间点准备好(设置一个值或者一个异常对象),若是不设置值或者异常,promise 对象在析构时会自动地设置一个 future_error 异常(broken_promise)来设置其自身的准备状态。上面的例子中已经提到了 get_future,此处再也不重复。

std::promise::set_value 介绍

generic template (1)
void set_value (const T& val);
void set_value (T&& val);
specializations (2)
void promise<R&>::set_value (R& val);   // when T is a reference type (R&)
void promise<void>::set_value (void);   // when T is void

设置共享状态的值,此后 promise 的共享状态标志变为 ready.

 std::promise::set_exception 介绍

为 promise 设置异常,此后 promise 的共享状态变标志变为 ready,例子以下,线程1从终端接收一个整数,线程2将该整数打印出来,若是线程1接收一个非整数,则为 promise 设置一个异常(failbit) ,线程2 在std::future::get 是抛出该异常。

复制代码
#include <iostream>       // std::cin, std::cout, std::ios
#include <functional>     // std::ref
#include <thread>         // std::thread
#include <future>         // std::promise, std::future
#include <exception>      // std::exception, std::current_exception

void get_int(std::promise<int>& prom) {
    int x;
    std::cout << "Please, enter an integer value: ";
    std::cin.exceptions (std::ios::failbit);   // throw on failbit
    try {
        std::cin >> x;                         // sets failbit if input is not int
        prom.set_value(x);
    } catch (std::exception&) {
        prom.set_exception(std::current_exception());
    }
}

void print_int(std::future<int>& fut) {
    try {
        int x = fut.get();
        std::cout << "value: " << x << '\n';
    } catch (std::exception& e) {
        std::cout << "[exception caught: " << e.what() << "]\n";
    }
}

int main ()
{
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();

    std::thread th1(get_int, std::ref(prom));
    std::thread th2(print_int, std::ref(fut));

    th1.join();
    th2.join();
    return 0;
}
复制代码

std::promise::set_value_at_thread_exit 介绍

设置共享状态的值,可是不将共享状态的标志设置为 ready,当线程退出时该 promise 对象会自动设置为 ready。若是某个 std::future 对象与该 promise 对象的共享状态相关联,而且该 future 正在调用 get,则调用 get 的线程会被阻塞,当线程退出时,调用 future::get 的线程解除阻塞,同时 get 返回 set_value_at_thread_exit 所设置的值。注意,该函数已经设置了 promise 共享状态的值,若是在线程结束以前有其余设置或者修改共享状态的值的操做,则会抛出 future_error( promise_already_satisfied )。

std::promise::swap 介绍

交换 promise 的共享状态。

上一讲《C++11 并发指南四(<future> 详解一 std::promise 介绍)》主要介绍了 <future> 头文件中的 std::promise 类,本文主要介绍 std::packaged_task。

std::packaged_task 包装一个可调用的对象,而且容许异步获取该可调用对象产生的结果,从包装可调用对象意义上来说,std::packaged_task 与 std::function 相似,只不过 std::packaged_task 将其包装的可调用对象的执行结果传递给一个 std::future 对象(该对象一般在另一个线程中获取 std::packaged_task 任务的执行结果)。

std::packaged_task 对象内部包含了两个最基本元素,1、被包装的任务(stored task),任务(task)是一个可调用的对象,如函数指针、成员函数指针或者函数对象,2、共享状态(shared state),用于保存任务的返回值,能够经过 std::future 对象来达到异步访问共享状态的效果。

能够经过 std::packged_task::get_future 来获取与共享状态相关联的 std::future 对象。在调用该函数以后,两个对象共享相同的共享状态,具体解释以下:

  • std::packaged_task 对象是异步 Provider,它在某一时刻经过调用被包装的任务来设置共享状态的值。
  • std::future 对象是一个异步返回对象,经过它能够得到共享状态的值,固然在必要的时候须要等待共享状态标志变为 ready.

std::packaged_task 的共享状态的生命周期一直持续到最后一个与之相关联的对象被释放或者销毁为止。下面一个小例子大体讲了 std::packaged_task 的用法:

复制代码
#include <iostream>     // std::cout
#include <future>       // std::packaged_task, std::future
#include <chrono>       // std::chrono::seconds
#include <thread>       // std::thread, std::this_thread::sleep_for

// count down taking a second for each value:
int countdown (int from, int to) {
    for (int i=from; i!=to; --i) {
        std::cout << i << '\n';
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    std::cout << "Finished!\n";
    return from - to;
}

int main ()
{
    std::packaged_task<int(int,int)> task(countdown); // 设置 packaged_task
    std::future<int> ret = task.get_future(); // 得到与 packaged_task 共享状态相关联的 future 对象.

    std::thread th(std::move(task), 10, 0);   //建立一个新线程完成计数任务.

    int value = ret.get();                    // 等待任务完成并获取结果.

    std::cout << "The countdown lasted for " << value << " seconds.\n";

    th.join();
    return 0;
}
复制代码

执行结果为:

复制代码
concurrency ) ./Packaged_Task1 
10
9
8
7
6
5
4
3
2
1
Finished!
The countdown lasted for 10 seconds.
复制代码

std::packaged_task 构造函数

default (1)
packaged_task() noexcept;
initialization (2)
template <class Fn>
  explicit packaged_task (Fn&& fn);
with allocator (3)
template <class Fn, class Alloc>
  explicit packaged_task (allocator_arg_t aa, const Alloc& alloc, Fn&& fn);
copy [deleted] (4)
packaged_task (const packaged_task&) = delete;
move (5)
packaged_task (packaged_task&& x) noexcept;

std::packaged_task 构造函数共有 5 中形式,不过拷贝构造已经被禁用了。下面简单地介绍一下上述几种构造函数的语义:

  1. 默认构造函数,初始化一个空的共享状态,而且该 packaged_task 对象无包装任务。
  2. 初始化一个共享状态,而且被包装任务由参数 fn 指定。
  3. 带自定义内存分配器的构造函数,与默认构造函数相似,可是使用自定义分配器来分配共享状态。
  4. 拷贝构造函数,被禁用。
  5. 移动构造函数。

下面例子介绍了各种构造函数的用法:

复制代码
#include <iostream>     // std::cout
#include <utility>      // std::move
#include <future>       // std::packaged_task, std::future
#include <thread>       // std::thread

int main ()
{
    std::packaged_task<int(int)> foo; // 默认构造函数.

    // 使用 lambda 表达式初始化一个 packaged_task 对象.
    std::packaged_task<int(int)> bar([](int x){return x*2;});

    foo = std::move(bar); // move-赋值操做,也是 C++11 中的新特性.

    // 获取与 packaged_task 共享状态相关联的 future 对象.
    std::future<int> ret = foo.get_future();

    std::thread(std::move(foo), 10).detach(); // 产生线程,调用被包装的任务.

    int value = ret.get(); // 等待任务完成并获取结果.
    std::cout << "The double of 10 is " << value << ".\n";

return 0;
}
复制代码

与 std::promise 相似, std::packaged_task 也禁用了普通的赋值操做运算,只容许 move 赋值运算。

std::packaged_task::valid 介绍

检查当前 packaged_task 是否和一个有效的共享状态相关联,对于由默认构造函数生成的 packaged_task 对象,该函数返回 false,除非中间进行了 move 赋值操做或者 swap 操做。

请看下例:

复制代码
#include <iostream>     // std::cout
#include <utility>      // std::move
#include <future>       // std::packaged_task, std::future
#include <thread>       // std::thread

// 在新线程中启动一个 int(int) packaged_task.
std::future<int> launcher(std::packaged_task<int(int)>& tsk, int arg)
{
    if (tsk.valid()) {
        std::future<int> ret = tsk.get_future();
        std::thread (std::move(tsk),arg).detach();
        return ret;
    }
    else return std::future<int>();
}

int main ()
{
    std::packaged_task<int(int)> tsk([](int x){return x*2;});

    std::future<int> fut = launcher(tsk,25);

    std::cout << "The double of 25 is " << fut.get() << ".\n";

    return 0;
}
复制代码

std::packaged_task::get_future 介绍

返回一个与 packaged_task 对象共享状态相关的 future 对象。返回的 future 对象能够得到由另一个线程在该 packaged_task 对象的共享状态上设置的某个值或者异常。

请看例子(其实前面已经讲了 get_future 的例子):

复制代码
#include <iostream>     // std::cout
#include <utility>      // std::move
#include <future>       // std::packaged_task, std::future
#include <thread>       // std::thread

int main ()
{
    std::packaged_task<int(int)> tsk([](int x) { return x * 3; })); // package task

    std::future<int> fut = tsk.get_future();   // 获取 future 对象.

    std::thread(std::move(tsk), 100).detach();   // 生成新线程并调用packaged_task.

    int value = fut.get();                     // 等待任务完成, 并获取结果.

    std::cout << "The triple of 100 is " << value << ".\n";

    return 0;
}
复制代码

std::packaged_task::operator()(Args... args) 介绍

调用该 packaged_task 对象所包装的对象(一般为函数指针,函数对象,lambda 表达式等),传入的参数为 args. 调用该函数通常会发生两种状况:

  • 若是成功调用 packaged_task 所包装的对象,则返回值(若是被包装的对象有返回值的话)被保存在 packaged_task 的共享状态中。
  • 若是调用 packaged_task 所包装的对象失败,而且抛出了异常,则异常也会被保存在 packaged_task 的共享状态中。

以上两种状况都使共享状态的标志变为 ready,所以其余等待该共享状态的线程能够获取共享状态的值或者异常并继续执行下去。

共享状态的值能够经过在 future 对象(由 get_future得到)上调用 get 来得到。

因为被包装的任务在 packaged_task 构造时指定,所以调用 operator() 的效果由 packaged_task 对象构造时所指定的可调用对象来决定:

  • 若是被包装的任务是函数指针或者函数对象,调用 std::packaged_task::operator() 只是将参数传递给被包装的对象。
  • 若是被包装的任务是指向类的非静态成员函数的指针,那么 std::packaged_task::operator() 的第一个参数应该指定为成员函数被调用的那个对象,剩余的参数做为该成员函数的参数。
  • 若是被包装的任务是指向类的非静态成员变量,那么 std::packaged_task::operator() 只容许单个参数。

std::packaged_task::make_ready_at_thread_exit 介绍

该函数会调用被包装的任务,并向任务传递参数,相似 std::packaged_task 的 operator() 成员函数。可是与 operator() 函数不一样的是,make_ready_at_thread_exit 并不会当即设置共享状态的标志为 ready,而是在线程退出时设置共享状态的标志。

若是与该 packaged_task 共享状态相关联的 future 对象在 future::get 处等待,则当前的 future::get 调用会被阻塞,直到线程退出。而一旦线程退出,future::get 调用继续执行,或者抛出异常。

注意,该函数已经设置了 promise 共享状态的值,若是在线程结束以前有其余设置或者修改共享状态的值的操做,则会抛出 future_error( promise_already_satisfied )。

std::packaged_task::reset() 介绍

重置 packaged_task 的共享状态,可是保留以前的被包装的任务。请看例子,该例子中,packaged_task 被重用了屡次:

复制代码
#include <iostream>     // std::cout
#include <utility>      // std::move
#include <future>       // std::packaged_task, std::future
#include <thread>       // std::thread

// a simple task:
int triple (int x) { return x*3; }

int main ()
{
    std::packaged_task<int(int)> tsk (triple); // package task


    std::future<int> fut = tsk.get_future();
    std::thread (std::move(tsk), 100).detach();
    std::cout << "The triple of 100 is " << fut.get() << ".\n";


    // re-use same task object:
    tsk.reset();
    fut = tsk.get_future();
    std::thread(std::move(tsk), 200).detach();
    std::cout << "Thre triple of 200 is " << fut.get() << ".\n";

    return 0;
}
复制代码

std::packaged_task::swap() 介绍

交换 packaged_task 的共享状态。

好了,std::packaged_task 介绍到这里,本文参考了 http://www.cplusplus.com/reference/future/packaged_task/ 相关的内容。后一篇文章我将向你们介绍 std::future,std::shared_future 以及 std::future_error,另外还会介绍 <future> 头文件中的 std::async,std::future_category 函数以及相关枚举类型。

上一讲《C++11 并发指南四(<future> 详解二 std::packaged_task 介绍)》主要介绍了 <future> 头文件中的 std::packaged_task 类,本文主要介绍 std::future,std::shared_future 以及 std::future_error,另外还会介绍 <future> 头文件中的 std::async,std::future_category 函数以及相关枚举类型。

std::future 介绍

前面已经屡次提到过 std::future,那么 std::future 到底是什么呢?简单地说,std::future 能够用来获取异步任务的结果,所以能够把它当成一种简单的线程间同步的手段。std::future 一般由某个 Provider 建立,你能够把 Provider 想象成一个异步任务的提供者,Provider 在某个线程中设置共享状态的值,与该共享状态相关联的 std::future 对象调用 get(一般在另一个线程中) 获取该值,若是共享状态的标志不为 ready,则调用 std::future::get 会阻塞当前的调用者,直到 Provider 设置了共享状态的值(此时共享状态的标志变为 ready),std::future::get 返回异步任务的值或异常(若是发生了异常)。

一个有效(valid)的 std::future 对象一般由如下三种 Provider 建立,并和某个共享状态相关联。Provider 能够是函数或者类,其实咱们前面都已经提到了,他们分别是:

一个 std::future 对象只有在有效(valid)的状况下才有用(useful),由 std::future 默认构造函数建立的 future 对象不是有效的(除非当前非有效的 future 对象被 move 赋值另外一个有效的 future 对象)。

 在一个有效的 future 对象上调用 get 会阻塞当前的调用者,直到 Provider 设置了共享状态的值或异常(此时共享状态的标志变为 ready),std::future::get 将返回异步任务的值或异常(若是发生了异常)。

下面以一个简单的例子说明上面一段文字吧(参考):

复制代码
// future example
#include <iostream>             // std::cout
#include <future>               // std::async, std::future
#include <chrono>               // std::chrono::milliseconds

// a non-optimized way of checking for prime numbers:
bool
is_prime(int x)
{
    for (int i = 2; i < x; ++i)
        if (x % i == 0)
            return false;
    return true;
}

int
main()
{
    // call function asynchronously:
    std::future < bool > fut = std::async(is_prime, 444444443);

    // do something while waiting for function to set future:
    std::cout << "checking, please wait";
    std::chrono::milliseconds span(100);
    while (fut.wait_for(span) == std::future_status::timeout)
        std::cout << '.';

    bool x = fut.get();         // retrieve return value

    std::cout << "\n444444443 " << (x ? "is" : "is not") << " prime.\n";

    return 0;
}
复制代码

 std::future 成员函数

std::future 构造函数

std::future 通常由 std::async, std::promise::get_future, std::packaged_task::get_future 建立,不过也提供了构造函数,以下表所示:

default (1)
future() noexcept;
copy [deleted] (2)
future (const future&) = delete;
move (3)
future (future&& x) noexcept;

 

不过 std::future 的拷贝构造函数是被禁用的,只提供了默认的构造函数和 move 构造函数(注:C++ 新特新)。另外,std::future 的普通赋值操做也被禁用,只提供了 move 赋值操做。以下代码所示:

 std::future<int> fut;           // 默认构造函数
  fut = std::async(do_some_task);   // move-赋值操做。

std::future::share()

返回一个 std::shared_future 对象(本文后续内容将介绍 std::shared_future ),调用该函数以后,该 std::future 对象自己已经不和任何共享状态相关联,所以该 std::future 的状态再也不是 valid 的了。

复制代码
#include <iostream>       // std::cout
#include <future>         // std::async, std::future, std::shared_future

int do_get_value() { return 10; }

int main ()
{
    std::future<int> fut = std::async(do_get_value);
    std::shared_future<int> shared_fut = fut.share();

    // 共享的 future 对象能够被屡次访问.
    std::cout << "value: " << shared_fut.get() << '\n';
    std::cout << "its double: " << shared_fut.get()*2 << '\n';

    return 0;
}
复制代码

std::future::get()

std::future::get 一共有三种形式,以下表所示(参考):

generic template (1)
T get();
reference specialization (2)
R& future<R&>::get();       // when T is a reference type (R&)
void specialization (3)
void future<void>::get();   // when T is void

当与该 std::future 对象相关联的共享状态标志变为 ready 后,调用该函数将返回保存在共享状态中的值,若是共享状态的标志不为 ready,则调用该函数会阻塞当前的调用者,而此后一旦共享状态的标志变为 ready,get 返回 Provider 所设置的共享状态的值或者异常(若是抛出了异常)。

请看下面的程序:

复制代码
#include <iostream>       // std::cin, std::cout, std::ios
#include <functional>     // std::ref
#include <thread>         // std::thread
#include <future>         // std::promise, std::future
#include <exception>      // std::exception, std::current_exception

void get_int(std::promise<int>& prom) {
    int x;
    std::cout << "Please, enter an integer value: ";
    std::cin.exceptions (std::ios::failbit);   // throw on failbit
    try {
        std::cin >> x;                         // sets failbit if input is not int
        prom.set_value(x);
    } catch (std::exception&) {
        prom.set_exception(std::current_exception());
    }
}

void print_int(std::future<int>& fut) {
    try {
        int x = fut.get();
        std::cout << "value: " << x << '\n';
    } catch (std::exception& e) {
        std::cout << "[exception caught: " << e.what() << "]\n";
    }
}

int main ()
{
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();

    std::thread th1(get_int, std::ref(prom));
    std::thread th2(print_int, std::ref(fut));

    th1.join();
    th2.join();
    return 0;
}
复制代码

std::future::valid()

检查当前的 std::future 对象是否有效,即释放与某个共享状态相关联。一个有效的 std::future 对象只能经过 std::async(), std::future::get_future 或者 std::packaged_task::get_future 来初始化。另外由 std::future 默认构造函数建立的 std::future 对象是无效(invalid)的,固然经过 std::future 的 move 赋值后该 std::future 对象也能够变为 valid。

复制代码
#include <iostream>       // std::cout
#include <future>         // std::async, std::future
#include <utility>        // std::move

int do_get_value() { return 11; }

int main ()
{
    // 由默认构造函数建立的 std::future 对象,
    // 初始化时该 std::future 对象处于为 invalid 状态.
    std::future<int> foo, bar;
    foo = std::async(do_get_value); // move 赋值, foo 变为 valid.
    bar = std::move(foo); // move 赋值, bar 变为 valid, 而 move 赋值之后 foo 变为 invalid.

    if (foo.valid())
        std::cout << "foo's value: " << foo.get() << '\n';
    else
        std::cout << "foo is not valid\n";

    if (bar.valid())
        std::cout << "bar's value: " << bar.get() << '\n';
    else
        std::cout << "bar is not valid\n";

    return 0;
}
复制代码

std::future::wait()

等待与当前std::future 对象相关联的共享状态的标志变为 ready.

若是共享状态的标志不是 ready(此时 Provider 没有在共享状态上设置值(或者异常)),调用该函数会被阻塞当前线程,直到共享状态的标志变为 ready。
一旦共享状态的标志变为 ready,wait() 函数返回,当前线程被解除阻塞,可是 wait() 并不读取共享状态的值或者异常。下面的代码说明了 std::future::wait() 的用法(参考

复制代码
#include <iostream>                // std::cout
#include <future>                // std::async, std::future
#include <chrono>                // std::chrono::milliseconds

// a non-optimized way of checking for prime numbers:
bool do_check_prime(int x) // 为了体现效果, 该函数故意没有优化.
{
    for (int i = 2; i < x; ++i)
        if (x % i == 0)
            return false;
    return true;
}

int main()
{
    // call function asynchronously:
    std::future < bool > fut = std::async(do_check_prime, 194232491);

    std::cout << "Checking...\n";
    fut.wait();

    std::cout << "\n194232491 ";
    if (fut.get()) // guaranteed to be ready (and not block) after wait returns
        std::cout << "is prime.\n";
    else
        std::cout << "is not prime.\n";

    return 0;
}
复制代码

执行结果以下:

concurrency ) ./Future-wait 
Checking...

194232491 is prime.
concurrency ) 

std::future::wait_for()

与 std::future::wait() 的功能相似,即等待与该 std::future 对象相关联的共享状态的标志变为 ready,该函数原型以下:

template <class Rep, class Period>
  future_status wait_for (const chrono::duration<Rep,Period>& rel_time) const;

而与 std::future::wait() 不一样的是,wait_for() 能够设置一个时间段 rel_time,若是共享状态的标志在该时间段结束以前没有被 Provider 设置为 ready,则调用 wait_for 的线程被阻塞,在等待了 rel_time 的时间长度后 wait_until() 返回,返回值以下:

返回值 描述
future_status::ready 共享状态的标志已经变为 ready,即 Provider 在共享状态上设置了值或者异常。
future_status::timeout 超时,即在规定的时间内共享状态的标志没有变为 ready。
future_status::deferred 共享状态包含一个 deferred 函数。

请看下面的例子:

复制代码
#include <iostream>                // std::cout
#include <future>                // std::async, std::future
#include <chrono>                // std::chrono::milliseconds

// a non-optimized way of checking for prime numbers:
bool do_check_prime(int x) // 为了体现效果, 该函数故意没有优化.
{
    for (int i = 2; i < x; ++i)
        if (x % i == 0)
            return false;
    return true;
}

int main()
{
    // call function asynchronously:
    std::future < bool > fut = std::async(do_check_prime, 194232491);

    std::cout << "Checking...\n";
    std::chrono::milliseconds span(1000); // 设置超时间隔.

    // 若是超时,则输出".",继续等待
    while (fut.wait_for(span) == std::future_status::timeout)
        std::cout << '.';

    std::cout << "\n194232491 ";
    if (fut.get()) // guaranteed to be ready (and not block) after wait returns
        std::cout << "is prime.\n";
    else
        std::cout << "is not prime.\n";

    return 0;
}
复制代码

std::future::wait_until()

与 std::future::wait() 的功能相似,即等待与该 std::future 对象相关联的共享状态的标志变为 ready,该函数原型以下:

template <class Rep, class Period>
  future_status wait_until (const chrono::time_point<Clock,Duration>& abs_time) const;

而 与 std::future::wait() 不一样的是,wait_until() 能够设置一个系统绝对时间点 abs_time,若是共享状态的标志在该时间点到来以前没有被 Provider 设置为 ready,则调用 wait_until 的线程被阻塞,在 abs_time 这一时刻到来以后 wait_for() 返回,返回值以下:

返回值 描述
future_status::ready 共享状态的标志已经变为 ready,即 Provider 在共享状态上设置了值或者异常。
future_status::timeout 超时,即在规定的时间内共享状态的标志没有变为 ready。
future_status::deferred 共享状态包含一个 deferred 函数。

 

std::shared_future 介绍

std::shared_future 与 std::future 相似,可是 std::shared_future 能够拷贝、多个 std::shared_future 能够共享某个共享状态的最终结果(即共享状态的某个值或者异常)。shared_future 能够经过某个 std::future 对象隐式转换(参见 std::shared_future 的构造函数),或者经过 std::future::share() 显示转换,不管哪一种转换,被转换的那个 std::future 对象都会变为 not-valid.

std::shared_future 构造函数

std::shared_future 共有四种构造函数,以下表所示:

default (1)
shared_future() noexcept;
copy (2)
shared_future (const shared_future& x);
move (3)
shared_future (shared_future&& x) noexcept;
move from future (4)
shared_future (future<T>&& x) noexcept;

最后 move from future(4) 即从一个有效的 std::future 对象构造一个 std::shared_future,构造以后 std::future 对象 x 变为无效(not-valid)。

std::shared_future 其余成员函数

std::shared_future 的成员函数和 std::future 大部分相同,以下(每一个成员函数都给出了链接):

std::future_error 介绍

class future_error : public logic_error;

std::future_error 继承子 C++ 标准异常体系中的 logic_error,有关 C++ 异常的继承体系,请参考相关的C++教程 ;-)。

其余与 std::future 相关的函数介绍

与 std::future 相关的函数主要是 std::async(),原型以下:

unspecified policy (1)
template <class Fn, class... Args>
  future<typename result_of<Fn(Args...)>::type>
    async(Fn&& fn, Args&&... args);
specific policy (2)
template <class Fn, class... Args>
  future<typename result_of<Fn(Args...)>::type>
    async(launch policy, Fn&& fn, Args&&... args);

上面两组 std::async() 的不一样之处是第一类 std::async 没有指定异步任务(即执行某一函数)的启动策略(launch policy),而第二类函数指定了启动策略,详见 std::launch 枚举类型,指定启动策略的函数的 policy 参数能够是launch::async,launch::deferred,以及二者的按位或( | )。

std::async() 的 fn 和 args 参数用来指定异步任务及其参数。另外,std::async() 返回一个 std::future 对象,经过该对象能够获取异步任务的值或异常(若是异步任务抛出了异常)。

下面介绍一下 std::async 的用法。

复制代码
#include <stdio.h>
#include <stdlib.h>

#include <cmath>
#include <chrono>
#include <future>
#include <iostream>

double ThreadTask(int n) {
    std::cout << std::this_thread::get_id()
        << " start computing..." << std::endl;

    double ret = 0;
    for (int i = 0; i <= n; i++) {
        ret += std::sin(i);
    }

    std::cout << std::this_thread::get_id()
        << " finished computing..." << std::endl;
    return ret;
}

int main(int argc, const char *argv[])
{
    std::future<double> f(std::async(std::launch::async, ThreadTask, 100000000));

#if 0
    while(f.wait_until(std::chrono::system_clock::now() + std::chrono::seconds(1))
            != std::future_status::ready) {
        std::cout << "task is running...\n";
    }
#else
    while(f.wait_for(std::chrono::seconds(1))
            != std::future_status::ready) {
        std::cout << "task is running...\n";
    }
#endif

    std::cout << f.get() << std::endl;

    return EXIT_SUCCESS;
}
复制代码

 

其余与 std::future 相关的枚举类介绍

下面介绍与 std::future 相关的枚举类型。与 std::future 相关的枚举类型包括:

enum class future_errc;
enum class future_status;
enum class launch;

下面分别介绍以上三种枚举类型:

std::future_errc 类型

std::future_errc 类型描述以下(参考):

类型
取值
描述
broken_promise 0 与该 std::future 共享状态相关联的 std::promise 对象在设置值或者异常以前一被销毁。
future_already_retrieved 1 与该 std::future 对象相关联的共享状态的值已经被当前 Provider 获取了,即调用了 std::future::get 函数。
promise_already_satisfied 2 std::promise 对象已经对共享状态设置了某一值或者异常。
no_state 3 无共享状态。

std::future_status 类型(参考

std::future_status 类型主要用在 std::future(或std::shared_future)中的 wait_for 和 wait_until 两个函数中的。

类型 取值
描述
future_status::ready 0 wait_for(或wait_until) 由于共享状态的标志变为 ready 而返回。
future_status::timeout 1 超时,即 wait_for(或wait_until) 由于在指定的时间段(或时刻)内共享状态的标志依然没有变为 ready 而返回。
future_status::deferred 2 共享状态包含了 deferred 函数。

std::launch 类型

该枚举类型主要是在调用 std::async 设置异步任务的启动策略的。

类型 描述
launch::async Asynchronous: 异步任务会在另一个线程中调用,并经过共享状态返回异步任务的结果(通常是调用 std::future::get() 获取异步任务的结果)。
launch::deferred Deferred: 异步任务将会在共享状态被访问时调用,至关与按需调用(即延迟(deferred)调用)。

请看下例(参考):

复制代码
#include <iostream>                // std::cout
#include <future>                // std::async, std::future, std::launch
#include <chrono>                // std::chrono::milliseconds
#include <thread>                // std::this_thread::sleep_for

void
do_print_ten(char c, int ms)
{
    for (int i = 0; i < 10; ++i) {
        std::this_thread::sleep_for(std::chrono::milliseconds(ms));
        std::cout << c;
    }
}

int
main()
{
    std::cout << "with launch::async:\n";
    std::future < void >foo =
        std::async(std::launch::async, do_print_ten, '*', 100);
    std::future < void >bar =
        std::async(std::launch::async, do_print_ten, '@', 200);
    // async "get" (wait for foo and bar to be ready):
    foo.get();
    bar.get();
    std::cout << "\n\n";

    std::cout << "with launch::deferred:\n";
    foo = std::async(std::launch::deferred, do_print_ten, '*', 100);
    bar = std::async(std::launch::deferred, do_print_ten, '@', 200);
    // deferred "get" (perform the actual calls):
    foo.get();
    bar.get();
    std::cout << '\n';

    return 0;
}
复制代码

在个人机器上执行结果:

with launch::async:
*@**@**@**@**@*@@@@@

with launch::deferred:
**********@@@@@@@@@@
相关文章
相关标签/搜索