C++11多线程thread

1.1 线程thread

std::thread 在 #include 头文件中声明,因此使用 std::thread 时需要包含 #include 头文件。

1.1.1 语法

1. 默认构造函数

//创建一个空的 thread 执行对象。
thread() _NOEXCEPT
{ // construct with no thread
_Thr_set_null(_Thr);
}

2. 初始化构造函数

// 创建std::thread执行对象,该thread对象可被joinable,新产生的线程会调用threadFun函数,该函数的参数由 args 给出
template<class Fn, class... Args>
explicit thread(Fn&& fn, Args&&... args);

  1. 拷贝构造函数
// 拷贝构造函数(被禁用),意味着 thread 不可被拷贝构造。
thread(const thread&) = delete;
thread t1;
thread t2 =t1; // 错误

4. move构造函数

//move 构造函数,调用成功之后 x 不代表任何 thread 执行对象。
// 注意:可被 joinable 的 thread 对象必须在他们销毁之前被主线程 join 或者将其设置为detached。
thread(thread&& x)noexcept
thread t1;
thread t2 =move(t1); // 可以


#include<thread>
using namespace std;
	
void threadFun(int &a) // 引用传递
{
	cout << "this is thread fun !" <<endl;
	cout <<" a = "<<(a+=10)<<endl;
}
int main()
{
	int x = 10;
	thread t1(threadFun, std::ref(x));
	thread t2(std::move(t1)); // t1 线程失去所有权
	thread t3;
	t3 = std::move(t2); // t2 线程失去所有权
	//t1.join(); // ?  // 错误的
	t3.join();
	cout<<"Main End "<<"x = "<<x<<endl;
	return 0;
}


5. 主要成员函数

  • get_id()
    获取线程ID,返回类型std::thread::id对象。添加链接描述

  • joinable()
    判断线程是否可以加入等待
    (http://www.cplusplus.com/reference/thread/thread/joinable/)

  • join()
    等该线程执行完成后才返回。
    http://www.cplusplus.com/reference/thread/thread/join/

  • detach()
    detach调用之后,目标线程就成为了守护线程,驻留后台运行,与之关联的std::thread对象失去对目标线程的关联,无法再通过std::thread对象取得该线程的控制权。当线程主函数执行完之后,线程就结束了,运行时库负责清理与该线程相关的资源。

    调用 detach 函数之后:
    *this 不再代表任何的线程执行实例。
    joinable() == false
    get_id() == std::thread::id()
    (http://www.cplusplus.com/reference/thread/thread/detach/)

1.1.2 简单线程的创建

  1. 传入0个值
  2. 传入2个值
  3. 传入引用
  4. 传入类函数
  5. detach
  6. move

使用std::thread创建线程,提供线程函数或者函数对象,并可以同时指定线程函数的参数。

范例:1-thread1-simple

#include <iostream>
#include <thread>
#include <string>

using namespace std;

// 1 传入0个值
void func1()
{
    cout << "func1 into" << endl;
}
// 2 传入2个值
void func2(int a, int b)
{
    cout << "func2 a + b = " << a + b << endl;
}
// 3 传入引用
void func3(int &c) // 引用传递
{
    cout << "func3 c = " << &c << endl;
    c += 10;
}

class A
{
public:
    void func4(int a)
    {
        cout << "thread: " << name_ << ", func4 a = " << a << endl;
    }

    void setName(string name)
    {
        name_ = name;
    }
    void displayName()
    {
        cout << "this:" << this << ", name:" << name_ << endl;
    }
    void play()
    {
        std::cout << "play call!" << std::endl;
    }

private:
    string name_;
};

void func5()
{
    cout << "func5 into sleep " << endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    cout << "func5 leave " << endl;
}
// 6. move
void func6()
{
    cout << "this is func6 !" << endl;
}

int main()
{
    // 1. 传入0个值
    cout << "\n\n main1--------------------------\n";
    std::thread t1(func1);
    t1.join();
    // 2. 传入2个值
    cout << "\n\n main2--------------------------\n";
    int a = 10;
    int b = 20;
    std::thread t2(func2, a, b);
    t2.join();

    // 3.传入引用
    cout << "\n\n main3--------------------------\n";
    int c = 10;
    std::thread t3(func3, std::ref(c));
    t3.join();
    cout << "main3 c = " << &c << ", " << c << endl;

    // 传入类函数
    cout << "\n\n main4--------------------------\n";
    A *a4_ptr = new A();
    a4_ptr->setName("mao.");
    std::thread t4(&A::func4, a4_ptr, 10);
    t4.join();
    delete a4_ptr;

    // detach
    cout << "\n\n main5--------------------------\n";
    std::thread t5(&func5);
    t5.detach(); // 脱离
    std::this_thread::sleep_for(std::chrono::seconds(2));
    cout << "\n main5 end\n";

    // move
    cout << "\n\n main6--------------------------\n";
    int x = 10;
    thread t6_1(func6);
    thread t6_2(std::move(t6_1)); // t6_1 线程失去所有权
    // t6_1.join();                  // 抛出异常
    t6_2.join();
    return 0;
}

1.1.3 线程封装

见范例:
zero_thread.h

#ifndef ZERO_THREAD_H
#define ZERO_THREAD_H
#include <thread>

class ZERO_Thread
{
public:
    ZERO_Thread();          // 构造函数
    virtual ~ZERO_Thread(); // 析构函数
    bool start();
    void stop();
    bool isAlive() const;
    std::thread::id id() { return th_->get_id(); }
    std::thread *getTthread() { return th_; }
    void join();   // 等待当前线程结束,不能在当前线程上调用
    void detach(); // 能在当前线程上调用
    static size_t CURRENT_THREADID();

protected:
    void threadEntry();
    virtual void run() = 0;

protected:
    bool running_; // 是否在运行
    std::thread *th_;
};

#endif /* ZERO_THREAD_H */

zero_thread.cpp

#include "zero_thread.h"
#include <sstream>
#include <iostream>
#include <exception>

ZERO_Thread::ZERO_Thread() : running_(false), th_(NULL)
{
}

ZERO_Thread::~ZERO_Thread()
{
    if (th_ != NULL)
    {
        if (th_->joinable())
        {
            std::cout << "~ZERO_Thread detach\n";
            th_->detach();
        }

        delete th_;
        th_ = NULL;
    }
    std::cout << "~ZERO_Thread()" << std::endl;
}

bool ZERO_Thread::start()
{
    if (running_)
    {
        return false;
    }
    try
    {
        th_ = new std::thread(&ZERO_Thread::threadEntry, this);
    }
    catch (...)
    {
        throw "[ZERO_Thread::start] thread start error";
    }
    return true;
}

void ZERO_Thread::stop()
{
    running_ = false;
}

bool ZERO_Thread::isAlive() const
{
    return running_;
}

void ZERO_Thread::join()
{
    if (th_->joinable())
    {
        th_->join(); // 不是detach才去join
    }
}

void ZERO_Thread::detach()
{
    th_->detach();
}

size_t ZERO_Thread::CURRENT_THREADID()
{
    // 声明为thread_local的本地变量在线程中是持续存在的,不同于普通临时变量的生命周期,
    // 它具有static变量一样的初始化特征和生命周期,即使它不被声明为static。
    static thread_local size_t threadId = 0;
    if (threadId == 0)
    {
        std::stringstream ss;
        ss << std::this_thread::get_id();
        threadId = strtol(ss.str().c_str(), NULL, 0);
    }
    return threadId;
}

void ZERO_Thread::threadEntry()
{
    running_ = true;
    try
    {
        run(); // 函数运行所在
    }
    catch (std::exception &ex)
    {
        running_ = false;
        throw ex;
    }
    catch (...)
    {
        running_ = false;
        throw;
    }
    running_ = false;
}

main.cpp

#include <iostream>
#include <chrono>
#include "zero_thread.h"
using namespace std;

class A : public ZERO_Thread
{
public:

    void run()
    {
        while (running_)
        {
            cout << "print A " << endl;
            std::this_thread::sleep_for(std::chrono::seconds(5));
        }
        cout << "----- leave A " << endl;
    }
};


class B : public ZERO_Thread
{
public:
    void run()
    {
        while (running_)
        {
            cout << "print B " << endl;
            std::this_thread::sleep_for(std::chrono::seconds(2));
        }
        cout << "----- leave B " << endl;
    }
};

int main()
{
    A a;
    a.start();

    B b;
    b.start();
    std::this_thread::sleep_for(std::chrono::seconds(5));

    a.stop();
    a.join();

     b.stop();
     b.join(); // 需要我们自己join
    return 0;
}

1.1.4 进一步了解

  1. get_id()
// thread::get_id / this_thread::get_id
#include <iostream>       // std::cout
#include <thread>         // std::thread, std::thread::id, std::this_thread::get_id
#include <chrono>         // std::chrono::seconds
 
std::thread::id main_thread_id = std::this_thread::get_id();

void is_main_thread() {
  if ( main_thread_id == std::this_thread::get_id() )
    std::cout << "This is the main thread.\n";
  else
    std::cout << "This is not the main thread.\n";
}

int main() 
{
  is_main_thread();
  std::thread th (is_main_thread);
  th.join();
}
  1. yield
// this_thread::yield example
#include <iostream>       // std::cout
#include <thread>         // std::thread, std::this_thread::yield
#include <atomic>         // std::atomic

std::atomic<bool> ready (false);

void count1m(int id) {
  while (!ready) {             // wait until main() sets ready...
    std::this_thread::yield();
  }
  for (volatile int i=0; i<1000000; ++i) {}
  std::cout << id;
}

int main ()
{
  std::thread threads[10];
  std::cout << "race of 10 threads that count to 1 million:\n";
  for (int i=0; i<10; ++i) threads[i]=std::thread(count1m,i);
  ready = true;               // go!
  for (auto& th : threads) th.join();
  std::cout << '\n';

  return 0;
}
  1. sleep_until
  2. sleep_for

详情见:(https://cplusplus.com/reference/thread/this_thread/)

1.2 互斥量

mutex又称互斥量,C++ 11中与 mutex相关的类(包括锁类型)和函数都声明在 #include头文件中,所以如果你需要使用 std::mutex,就必须包含 头文件。
C++11提供如下4种语义的互斥量(mutex)
std::mutex,独占的互斥量,不能递归使用。
std::time_mutex,带超时的独占互斥量,不能递归使用。
std::recursive_mutex,递归互斥量,不带超时功能。
std::recursive_timed_mutex,带超时的递归互斥量。

以下主要介绍独占互斥量std::mutex

1.2.1 std::mutex 介绍

下面以 std::mutex 为例介绍 C++11 中的互斥量用法。
std::mutex 是C++11 中最基本的互斥量,std::mutex 对象提供了独占所有权的特性——即不支持递归地对 std::mutex 对象上锁,而 std::recursive_lock 则可以递归地对互斥量对象上锁。

std::mutex 的成员函数

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

范例:

// 1-2-mutex1
#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 increases_10k()
{
    for (int i = 0; i < 10000; ++i)
    {
        // 1. 使用try_lock的情况
        if (mtx.try_lock())
        { // only increase if currently not locked:
            ++counter;
            mtx.unlock();
        }
        //     // 2. 使用lock的情况
        //     {
        //         mtx.lock();
        //         ++counter;
        //         mtx.unlock();
        //     }
    }
}

int main()
{
    std::thread threads[10];
    for (int i = 0; i < 10; i++)
    {
        threads[i] = std::thread(increases_10k);
    }
    for (auto &th : threads)
        th.join();

    std::cout << " successful increases of the counter " << counter << std::endl;

    return 0;
}

一般我们不单独使用mutex,下面介绍常用的: lock_guard和unique_lock

1.2.2 lock_guard和unique_lock的使用和区别

相对于手动lock和unlock,我们可以使用**RAII(通过类的构造析构)**来实现更好的编码方式。
RAII:也称为“资源获取就是初始化”,是c++等编程语言常用的管理资源、避免内存泄露的方法。它保证在任何情况下,使用对象时先构造对象,最后析构对象。

1. unique_lock,lock_guard的使用

#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::unique_lock<std::mutex> lck(mtx); // 这里的lock_guard换成unique_lock是一样的。
        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;
}

2. unique_lock,lock_guard的区别

  1. unique_lock与lock_guard都能实现自动加锁和解锁,但是前者更加灵活,能实现更多的功能。
  2. unique_lock可以进行临时解锁和再上锁,如在构造对象之后使用lck.unlock()就可以进行解锁,lck.lock()进行上锁,而不必等到析构时自动解锁。
#include <iostream>
#include <deque>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <unistd.h>

std::deque<int> q;
std::mutex mu;
std::condition_variable cond;
int count = 0;

void fun1()
{
    while (true)
    {
        {
            std::unique_lock<std::mutex> locker(mu);
            q.push_front(count++);
            cond.notify_one();
        }
        // locker.unlock();
        sleep(1);
    }
}

void fun2()
{
    while (true)
    {
        std::unique_lock<std::mutex> locker(mu);
        cond.wait(locker, []()
                  { return !q.empty(); });
        auto data = q.back();
        q.pop_back();
        std::cout << "thread2 get value form thread1: " << data << std::endl;
    }
}

int main()
{
    std::thread t1(fun1);
    std::thread t2(fun2);
    t1.join();
    t2.join();
    return 0;
}

条件变量的目的就是为了,在没有获得某种提醒时长时间休眠; 如果正常情况下, 我们需要一直循环(+sleep), 这样的问题就是CPU消耗+时延问题,条件变量的意思是在cond.wait这里一直休眠直到cond.notify_one唤醒才开始执行下一句; 还有cond.notify_all()接口用于唤醒所有等待的线程。

那么为什么必须使用unique_lock呢?
原因: 条件变量在wait时会进行unlock再进入休眠, lock_guard并无该操作接口
wait: 如果线程被唤醒或者超时那么会先进行lock获取锁, 再判断条件(传入的参数)是否成立, 如果成立则wait函数返回否则释放锁继续休眠
notify: 进行notify动作并不需要获取锁

使用场景:需要结合notify+wait的场景使用unique_lock; 如果只是单纯的互斥使用lock_guard

3. 总结

lock_guard

  1. std::lock_guard 在构造函数中进行加锁,析构函数中进行解锁。
  2. 锁在多线程编程中,使用较多,因此c++11提供了lock_guard模板类;在实际编程中,我们也可以根
    据自己的场景编写resource_guard RAII类,避免忘掉释放资源。

std::unique_lock

  1. unique_lock 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与
    条件变量一同使用。
  2. unique_lock比lock_guard使用更加灵活,功能更加强大。
  3. 使用unique_lock需要付出更多的时间、性能成本。

1.3条件变量

互斥量是多线程间同时访问某一共享变量时,保证变量可被安全访问的手段。但单靠互斥量无法实现线程的同步。线程同步是指线程间需要按照预定的先后次序顺序进行的行为。C++11对这种行为也提供了有力的支持,这就是条件变量。条件变量位于头文件condition_variable下。
(http://www.cplusplus.com/reference/condition_variable/condition_variable)

条件变量使用过程:

  1. 拥有条件变量的线程获取互斥量;
  2. 循环检查某个条件,如果条件不满足则阻塞直到条件满足;如果条件满足则向下执行;
  3. 某个线程满足条件执行完之后调用notify_one或notify_all唤醒一个或者所有等待线程。

条件变量提供了两类操作:wait和notify。这两类操作构成了多线程同步的基础。

1.3.1 成员函数

1 wait函数

函数原型

void wait (unique_lock<mutex>& lck);
template <class Predicate>
void wait (unique_lock<mutex>& lck, Predicate pred);

包含两种重载,第一种只包含unique_lock对象,另外一个Predicate 对象(等待条件),这里必须使用unique_lock,因为wait函数的工作原理:

  1. 当前线程调用wait()后将被阻塞并且函数会解锁互斥量,直到另外某个线程调用notify_one或者notify_all唤醒当前线程;一旦当前线程获得通知(notify),wait()函数也是自动调用lock(),同理不能使用lock_guard对象。
  2. 如果wait没有第二个参数,第一次调用默认条件不成立,直接解锁互斥量并阻塞到本行,直到某一个线程调用notify_one或notify_all为止,被唤醒后,wait重新尝试获取互斥量,如果得不到,线程会卡在这里,直到获取到互斥量,然后无条件地继续进行后面的操作。
  3. 如果wait包含第二个参数,如果第二个参数不满足,那么wait将解锁互斥量并堵塞到本行,直到某一个线程调用notify_one或notify_all为止,被唤醒后,wait重新尝试获取互斥量,如果得不到,线程会卡在这里,直到获取到互斥量,然后继续判断第二个参数,如果表达式为false,wait对互斥量解锁,然后休眠,如果为true,则进行后面的操作。

2 wait_for函数

函数原型:

template <class Rep, class Period>
cv_status wait_for (unique_lock<mutex>& lck,
const chrono::duration<Rep,Period>& rel_time);
template <class Rep, class Period, class Predicate>
bool wait_for (unique_lock<mutex>& lck,
const chrono::duration<Rep,Period>& rel_time, Predicate
pred);

和wait不同的是,wait_for可以执行一个时间段,在线程收到唤醒通知或者时间超时之前,该线程都会处于阻塞状态,如果收到唤醒通知或者时间超时,wait_for返回,剩下操作和wait类似。

3 wait_until函数

函数原型:

template <class Clock, class Duration>
cv_status wait_until (unique_lock<mutex>& lck,
const chrono::time_point<Clock,Duration>& abs_time);
template <class Clock, class Duration, class Predicate>
bool wait_until (unique_lock<mutex>& lck,
const chrono::time_point<Clock,Duration>& abs_time,
Predicate pred);

与wait_for类似,只是wait_until可以指定一个时间点,在当前线程收到通知或者指定的时间点超时之前,该线程都会处于阻塞状态。如果超时或者收到唤醒通知,wait_until返回,剩下操作和wait类似

4 notify_one函数

void notify_one() noexcept;

解锁正在等待当前条件的线程中的一个,如果没有线程在等待,则函数不执行任何操作,如果正在等待的线程多余一个,则唤醒的线程是不确定的。

5 notify_all函数

void notify_all() noexcept;

解锁正在等待当前条件的所有线程,如果没有正在等待的线程,则函数不执行任何操作。

1.3.2 范例

使用条件变量实现一个同步队列,同步队列作为一个线程安全的数据共享区,经常用于线程之间数据读取。

sync_queue.h


#ifndef SYNC_QUEUE_H
#define SYNC_QUEUE_H
#include <list>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <iostream>

template <typename T>
class SimpleSyncQueue
{
public:
    SimpleSyncQueue() {}
    void Put(const T &x)
    {
        std::lock_guard<std::mutex> locker(_mutex);
        _queue.push_back(x);
        _notEmpty.notify_one();
    }
    void Take(T &x)
    {
        std::unique_lock<std::mutex> locker(_mutex);
        _notEmpty.wait(locker, [this]
                       { return !_queue.empty(); });
        x = _queue.front();
        _queue.pop_front();
    }
    bool Empty()
    {
        std::lock_guard<std::mutex> locker(_mutex);
        return _queue.empty();
    }
    size_t Size()
    {
        std::lock_guard<std::mutex> locker(_mutex);
        return _queue.size();
    }

private:
    std::list<T> _queue;
    std::mutex _mutex;
    std::condition_variable _notEmpty; // 只用一个condition
};
#endif // SIMPLE_SYNC_QUEUE_H

** main.cpp **

#include <iostream>
#include "sync_queue.h"
#include <thread>
#include <iostream>
#include <mutex>
using namespace std;


SimpleSyncQueue<int> syncQueue;
void PutDatas()
{
    for (int i = 0; i < 20; ++i)
    {
        syncQueue.Put(888);
    }
}

void TakeDatas()
{
    int x = 0;
    for (int i = 0; i < 20; ++i)
    {
        syncQueue.Take(x);
        std::cout << x << std::endl;
    }
}
int main(void)
{
    std::thread t1(PutDatas);
    std::thread t2(TakeDatas);
    t1.join();
    t2.join();
    std::cout << "main finish\n";
    return 0;
}

1.4 原子变量

原子变量是C++中用于多线程编程的强大工具之一。它们提供了一种线程安全的方式来访问和修改共享数据,而无需使用显式的互斥锁。原子变量的操作是原子的,这意味着在执行原子操作时,没有其他线程可以干扰它。这样可以确保在多线程应用程序中对共享数据的访问是安全的。

原子变量的使用通常包括以下几个步骤:

  1. 定义原子变量类型:例如,std::atomic定义了一个整数类型的原子变量。
  2. 初始化原子变量:例如,std::atomic count(0)将计数器初始化为0。
  3. 访问和修改原子变量:例如,count++和count–都是原子操作,因为它们只能由一个线程在同一时间执行。

这里有一个例子:

#include <iostream>
#include <atomic>
#include <thread>

std::atomic<int> count(0);

void increase() {
    for (int i = 0; i < 10000; ++i) {
        ++count;
    }
}

int main() {
    std::thread t1(increase);
    std::thread t2(increase);

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

    std::cout << "Count: " << count << std::endl;

    return 0;
}

在这个例子中,我们定义了一个整数类型的原子变量count,并初始化为0。然后,我们创建了两个线程t1和t2,它们都调用了increase函数来增加计数器的值。最后,我们打印出计数器的值。由于我们使用了原子变量,所以最终的结果应该是20000。

https://cplusplus.com/reference/atomic/atomic/

1.5异步操作

  • std::future : 异步指向某个任务,然后通过future特性去获取任务函数的返回结果。
  • std::aysnc: 异步运行某个任务函数
  • std::packaged_task :将任务和feature绑定在一起的模板,是一种封装对任务的封装。
  • std::promise
    参考C++官方手册的范例。

1.5.1std::aysnc和std::future

std::future期待一个返回,从一个异步调用的角度来说,**future更像是执行函数的返回值,**C++标准库使用std::future为一次性事件建模,如果一个事件需要等待特定的一次性事件,那么这线程可以获取一个future对象来代表这个事件。
异步调用往往不知道何时返回,但是如果异步调用的过程需要同步,或者说后一个异步调用需要使用前一个异步调用的结果。这个时候就要用到future。
线程可以周期性的在这个future上等待一小段时间,检查future是否已经ready,如果没有,该线程可以先去做另一个任务,一旦future就绪,该future就无法复位(无法再次使用这个future等待这个事件),所以future代表的是一次性事件。

future的类型
在库的头文件中声明了两种future,唯一future(std::future)和共享future(std::shared_future)这两个是参照。
std::unique_ptr和std::shared_ptr设立的,前者的实例是仅有的一个指向其关联事件的实例,而后者可以有多个实例指向同一个关联事件,当事件就绪时,所有指向同一事件的std::shared_future实例会变成就绪。

future的使用

std::future是一个模板,例如std::future,模板参数就是期待返回的类型,虽然future被用于线程间通信,但其本身却并不提供同步访问,热门必须通过互斥元或其他同步机制来保护访问。
future使用的时机是当你不需要立刻得到一个结果的时候,你可以开启一个线程帮你去做一项任务,并期待这个任务的返回,但是std::thread并没有提供这样的机制,这就需要用到std::async和std::future(都在头文件中声明)
std::async返回一个std::future对象,而不是给你一个确定的值(所以当你不需要立刻使用此值的时候才需要用到这个机制)。当你需要使用这个值的时候,对future使用get(),线程就会阻塞直到future就绪,然后返回该值。

#include <iostream>
#include <future>
#include <thread>

using namespace std;

int find_result_to_add()
{
    // std::this_thread::sleep_for(std::chrono::seconds(2)); // 用来测试异步延迟的影响
    std::cout
        << "find_result_to_add" << std::endl;
    return 1 + 1;
}

int find_result_to_add2(int a, int b)
{
    // std::this_thread::sleep_for(std::chrono::seconds(5)); // 用来测试异步延迟的影响
     return a + b;
}

void do_other_things()
{
    std::cout << "do_other_things" << std::endl;
    // std::this_thread::sleep_for(std::chrono::seconds(5));
}

int main()
{
    // future<int> result = std::async(find_result_to_add);
    // future <decltype (find_result_to_add())> result = std::async(find_result_to_add);
    // future <decltype (find_result_to_add())>
    // auto result = std::async(find_result_to_add);

    // do_other_things();
    // cout << "result: " << result.get() << endl;

    // std::future<decltype(find_result_to_add2(int, int))> result2 =
    //     std::async(find_result_to_add2, 10, 20); // 错误
    std::future<decltype(find_result_to_add2(0, 0))> result2 =
        std::async(find_result_to_add2, 10, 20);
    std::cout << "result2: " << result2.get() << std::endl; // 延迟是否有影响?
    return 0;
}

跟thread类似,async允许你通过将额外的参数添加到调用中,来将附加参数传递给函数。如果传入的函数指针是某个类的成员函数,则还需要将类对象指针传入(直接传入,传入指针,或者是std::ref封装)。

默认情况下,std::async是否启动一个新线程,或者在等待future时,任务是否同步运行都取决于你给的参数。这个参数为std::launch类型.

  • std::launch::defered表明该函数会被延迟调用,直到在future上调用get()或者wait()为止
  • std::launch::async,表明函数会在自己创建的线程上运行
  • std::launch::any = std::launch::defered | std::launch::async
  • std::launch::sync = std::launch::defered
enum class launch
{
async,deferred,sync=deferred,any=async|deferred
};

PS:默认选项参数被设置为std::launch::any。如果函数被延迟运行可能永远都不会运行。

1.5.2 std::packaged_task

如果说std::async和std::feature还是分开看的关系的话,那么std::packaged_task就是将任务和feature绑定在一起的模板,是一种封装对任务的封装。
The class template std::packaged_task wraps any Callable target (function, lambda expression,bind expression, or another function object) so that it can be invoked asynchronously. Its return value or exception thrown is stored in a shared state hich can be accessed through std::future objects.
可以通过std::packaged_task对象获取任务相关联的feature,调用get_future()方法可以获得std::packaged_task对象绑定的函数的返回值类型的future。std::packaged_task的模板参数是函数签名。
PS:例如int add(int a, intb)的函数签名就是int(int, int)

#include <iostream>
#include <future>

using namespace std;

int add(int a, int b, int c)
{
    std::cout << "call add\n";
    return a + b + c;
}
void do_other_things()
{
    std::cout << "do_other_things" << std::endl;
}

int main()
{
    std::packaged_task<int(int, int, int)> task(add); // 封装任务
    do_other_things();
    std::future<int> result = task.get_future();
    task(1, 1, 2); // 必须要让任务执行,否则在get()获取future的值时会一直阻塞
    std::cout << "result:" << result.get() << std::endl;
    return 0;
}

1.5.3 std::promise

std::promise提供了一种设置值的方式,它可以在这之后通过相关联的std::future对象进行读取。换种说法,之前已经说过std::future可以读取一个异步函数的返回值了,那么这个std::promise就提供一种方式手动让future就绪。

#include <future>
#include <string>
#include <thread>
#include <iostream>
using namespace std;

void print(std::promise<std::string> &p)
{
    p.set_value("There is the result whitch you want.");
}
void do_some_other_things()
{
    std::cout << "Hello World" << std::endl;
}
int main()
{
    std::promise<std::string> promise;
    std::future<std::string> result = promise.get_future();
    std::thread t(print, std::ref(promise));
    do_some_other_things();
    std::cout << result.get() << std::endl;
    t.join();
    return 0;
}

由此可以看出在promise创建好的时候future也已经创建好了
线程在创建promise的同时会获得一个future,然后将promise传递给设置他的线程,当前线程则持有future,以便随时检查是否可以取值。

1.5.4 总结

future的表现为期望,当前线程持有future时,期望从future获取到想要的结果和返回,可以把future当做异步函数的返回值。而
promise是一个承诺,当线程创建了promise对象后,这个promise对象向线程承诺他必定会被人设置一个值,和promise相关联的future就是获取其返回的手段。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值