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);
- 拷贝构造函数
// 拷贝构造函数(被禁用),意味着 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 简单线程的创建
- 传入0个值
- 传入2个值
- 传入引用
- 传入类函数
- detach
- 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 进一步了解
- 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();
}
- 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;
}
- sleep_until
- 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 的成员函数
- 构造函数,std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于unlocked 状态的。
- lock(),调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况:(1). 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。(2). 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。(3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。
- unlock(), 解锁,释放对互斥量的所有权。
- 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的区别
- unique_lock与lock_guard都能实现自动加锁和解锁,但是前者更加灵活,能实现更多的功能。
- 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
- std::lock_guard 在构造函数中进行加锁,析构函数中进行解锁。
- 锁在多线程编程中,使用较多,因此c++11提供了lock_guard模板类;在实际编程中,我们也可以根
据自己的场景编写resource_guard RAII类,避免忘掉释放资源。
std::unique_lock
- unique_lock 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与
条件变量一同使用。 - unique_lock比lock_guard使用更加灵活,功能更加强大。
- 使用unique_lock需要付出更多的时间、性能成本。
1.3条件变量
互斥量是多线程间同时访问某一共享变量时,保证变量可被安全访问的手段。但单靠互斥量无法实现线程的同步。线程同步是指线程间需要按照预定的先后次序顺序进行的行为。C++11对这种行为也提供了有力的支持,这就是条件变量。条件变量位于头文件condition_variable下。
(http://www.cplusplus.com/reference/condition_variable/condition_variable)
条件变量使用过程:
- 拥有条件变量的线程获取互斥量;
- 循环检查某个条件,如果条件不满足则阻塞直到条件满足;如果条件满足则向下执行;
- 某个线程满足条件执行完之后调用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函数的工作原理:
- 当前线程调用wait()后将被阻塞并且函数会解锁互斥量,直到另外某个线程调用notify_one或者notify_all唤醒当前线程;一旦当前线程获得通知(notify),wait()函数也是自动调用lock(),同理不能使用lock_guard对象。
- 如果wait没有第二个参数,第一次调用默认条件不成立,直接解锁互斥量并阻塞到本行,直到某一个线程调用notify_one或notify_all为止,被唤醒后,wait重新尝试获取互斥量,如果得不到,线程会卡在这里,直到获取到互斥量,然后无条件地继续进行后面的操作。
- 如果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++中用于多线程编程的强大工具之一。它们提供了一种线程安全的方式来访问和修改共享数据,而无需使用显式的互斥锁。原子变量的操作是原子的,这意味着在执行原子操作时,没有其他线程可以干扰它。这样可以确保在多线程应用程序中对共享数据的访问是安全的。
原子变量的使用通常包括以下几个步骤:
- 定义原子变量类型:例如,std::atomic定义了一个整数类型的原子变量。
- 初始化原子变量:例如,std::atomic count(0)将计数器初始化为0。
- 访问和修改原子变量:例如,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就是获取其返回的手段。