1 多线程thread
1.1 线程thread
#include <thread>
1.1.1 构造函数
- 默认构造函数
//创建一个空的 thread 执行对象。 thread() _NOEXCEPT { // construct with no thread _Thr_set_null(_Thr); }
- 初始化构造函数
//创建std::thread执行对象,该thread对象可被joinable,新产生的线程会调用threadFun函数,该函 数的参数由 args 给出 template<class Fn, class... Args> explicit thread(Fn&& fn, Args&&... args);
- 拷贝构造函数
// 拷贝构造函数(被禁用),意味着 thread 不可被拷贝构造。 thread(const thread&) = delete;
- Move构造函数
// move 构造函数,调用成功之后 x 不代表任何 thread 执行对象。 // 注意:可被 joinable 的 thread 对象必须在他们销毁之前被主线程 join 或者将其设置为 detached。 thread(thread&& x)noexcept
1.1.2 主要成员函数
- get_id()
获取线程ID,返回类型std::thread::id对象。 - joinable()
判断线程是否可以加入等待 - join()
等该线程执行完成后才返回。 - detach()
将本线程从调用线程中分离出来,允许本线程独立执行。(但是当主进程结束的时候,即便是detach出去的子线程不管有没有完成都会被强制杀死)
1.1.3 简单线程的创建
#include <iostream>
#include <thread>
using namespace std;
void func1()
{
cout << "func1 into" << endl;
}
void func2(int a, int b)
{
cout << "func2 a + b = " << a+b << endl;
}
class A
{
public:
static void fun3(int a)
{
cout << "a = " << a << endl;
}
};
int main()
{
std::thread t1(func1); // 只传递函数
t1.join(); // 阻塞等待线程函数执行结束
int a =10;
int b =20;
std::thread t2(func2, a, b); // 加上参数传递
t2.join();
std::thread t3(&A::fun3, 1); // 绑定类静态函数
t3.join();
return 0;
}
1.1.4 线程封装
- 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* getThread() { return _th; }
void join(); // 等待当前线程结束, 不能在当前线程上调用
void detach(); //能在当前线程上调用
static size_t CURRENT_THREADID();
protected:
static void threadEntry(ZERO_Thread *pThread); // 静态函数, 线程入口
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)
{
//如果资源没有被detach或者被join,则自己释放
if (_th->joinable())
{
_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();
}
}
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(ZERO_Thread *pThread)
{
pThread->_running = true;
try
{
pThread->run(); // 函数运行所在
}
catch (std::exception &ex)
{
pThread->_running = false;
throw ex;
}
catch (...)
{
pThread->_running = false;
throw;
}
pThread->_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(10));
a.stop();
a.join();
b.stop();
b.join();
}
cout << "Hello World!" << endl;
return 0;
}
1.2 互斥量
#include <mutex>
C++11提供如下4种语义的互斥量(mutex)
std::mutex,独占的互斥量,不能递归使用。
std::time_mutex,带超时的独占互斥量,不能递归使用。
std::recursive_mutex,递归互斥量,不带超时功能。 不推荐使用
std::recursive_timed_mutex,带超时的递归互斥量。
1.2.1 独占互斥量std::mutex
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)。
#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;
}
1.2.2 递归互斥量std::recursive_mutex
递归锁允许同一个线程多次获取该互斥锁,可以用来解决同一线程需要多次获取互斥量时死锁的问题。
struct Complex
{
std::mutex mutex;
int i;
Complex() : i(0){}
void mul(int x)
{
std::lock_guard<std::mutex> lock(mutex);
i *= x;
}
void div(int x)
{
std::lock_guard<std::mutex> lock(mutex);
i /= x;
}
void both(int x, int y)
{
//lock_guard 构造函数加锁, 析构函数释放锁
std::lock_guard<std::mutex> lock(mutex);
mul(x); // 获取不了锁
div(y);
}
void init()
{
//lock_guard 构造函数加锁, 析构函数释放锁
std::lock_guard<std::mutex> lock(mutex);
sub_init();
}
void sub_init()
{
std::lock_guard<std::mutex> lock(mutex);
}
};
int main(void)
{
Complex complex;
complex.both(32, 23);
std::cout << "main finish\n";
return 0;
}
运行后出现死锁的情况。在调用both时获取了互斥量,只有在析构时才能释放,此时both里调用mul时又要获取互斥量,但both的没有释放,此时阻塞在mul获取锁的地方,从而产生死锁。
struct Complex
{
std::recursive_mutex mutex;
};
虽然递归锁能解决这种情况的死锁问题,但是尽量不要使用递归锁,主要原因如下:
- 需要用到递归锁的多线程互斥处理本身就是可以简化的,允许递归很容易放纵复杂逻辑的产生,并且产生晦涩,当要使用递归锁的时候应该重新审视自己的代码是否一定要使用递归锁;
- 递归锁比起非递归锁,效率会低;
- 递归锁虽然允许同一个线程多次获得同一个互斥量,但可重复获得的最大次数并未具体说明,一旦过一定的次数,再对lock进行调用就会抛出std::system错误。
1.2.3 带超时的互斥量std::timed_mutex和std::recursive_timed_mutex
timed_mutex 比 mutex 多了两个超时获取锁的接口:try_lock_for
和 try_lock_until
当mutex获取锁失败时,线程会一直阻塞。
timed_mutex带超时功能。在规定的等待时间内,没有获取锁,线程不会一直阻塞,代码会继续执行。try_lock_for参数是一个时间段,如100秒,try_lock_until参数是未来一个时间点。
timed_mutex t_lock;
if(t_lock.try_lock_for(std::chrono::milliseconds(100))){ //如果100ms内获得了锁就返回true
//chrono::milliseconds chrono是C++11下的一个时间库。
......
t_lock.unlock();
}else{
......
}
1.2.4 lock_guard和unique_lock的使用和区别
相对于手动 lock 和 unlock,我们可以使用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 {
// 使用局部 lock_guard 锁定 mtx 可以确保在销毁/异常时解锁
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;
}
这里的lock_guard换成unique_lock是一样的。
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;
void fun1() {
while (true) {
std::unique_lock<std::mutex> locker(mu);
q.push_front(count);
locker.unlock();
cond.notify_one();
sleep(10);
}
}
void fun2() {
while (true) {
std::unique_lock<std::mutex> locker(mu);
cond.wait(locker, [](){return !q.empty();});
data = q.back();
q.pop_back();
locker.unlock();
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动作并不需要获取锁
总结
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 条件变量
- 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 SyncQueue
{
private:
bool IsFull() const
{
return _queue.size() == _maxSize;
}
bool IsEmpty() const
{
return _queue.empty();
}
public:
SyncQueue(int maxSize) : _maxSize(maxSize) { }
void Put(const T& x)
{
/*----------------------------------------------------------------*/
std::lock_guard<std::mutex> locker(_mutex);
while (IsFull())
{
std::cout << "full wait... size " << _queue.size() << std::endl;
_notFull.wait(_mutex);
}
//std::lock_guard<std::mutex> locker(_mutex);
//_notFull.wait(_mutex, [this] {return !IsFull();});
// 先检查判断式是否满足,满足则重新获取mutex,不满足则释放mutex继续等待。
/*----------------------------------------------------------------*/
_queue.push_back(x);
_notEmpty.notify_one(); // put后非空,通知消费者
}
void Take(T& x)
{
std::lock_guard<std::mutex> locker(_mutex);
while (IsEmpty())
{
std::cout << "empty wait.." << std::endl;
_notEmpty.wait(_mutex);
}
x = _queue.front();
_queue.pop_front();
_notFull.notify_one(); // take后非满,通知生产者
}
bool Empty()
{
std::lock_guard<std::mutex> locker(_mutex);
return _queue.empty();
}
bool Full()
{
std::lock_guard<std::mutex> locker(_mutex);
return _queue.size() == _maxSize;
}
size_t Size()
{
std::lock_guard<std::mutex> locker(_mutex);
return _queue.size();
}
int Count()
{
return _queue.size();
}
private:
std::list<T> _queue; //缓冲区
std::mutex _mutex; //互斥量和条件变量结合起来使用
std::condition_variable_any _notEmpty;//不为空的条件变量
std::condition_variable_any _notFull; //没有满的条件变量
int _maxSize; //同步队列最大的size
};
#endif // SYNC_QUEUE_H
- main.cpp
#include <iostream>
#include "sync_queue.h"
#include <thread>
#include <iostream>
#include <mutex>
using namespace std;
SyncQueue<int> syncQueue(5);
void PutDatas()
{
for (int i = 0; i < 20; ++i)
{
syncQueue.Put(i);
}
std::cout << "PutDatas finish\n";
}
void TakeDatas()
{
int x = 0;
for (int i = 0; i < 20; ++i)
{
syncQueue.Take(x);
std::cout << x << std::endl;
}
std::cout << "TakeDatas finish\n";
}
int main(void)
{
std::thread t1(PutDatas); // 生产线程
std::thread t2(TakeDatas); // 消费线程
t1.join();
t2.join();
std::cout << "main finish\n";
return 0;
}
这里需要注意的是,wait函数中会释放mutex,而lock_guard这时还拥有mutex,它只会在出了作用域
之后才会释放mutex,所以这时它并不会释放,但执行wait时会提前释放mutex。
从语义上看这里使用lock_guard会产生矛盾,但是实际上并不会出问题,因为wait提前释放锁之后会处
于等待状态,在被notify_one或者notify_all唤醒后会先获取mutex,这相当于lock_guard的mutex在释放之后又获取到了,因此,在出了作用域之后lock_guard自动释放mutex不会有问题。
这里应该用unique_lock
,因为unique_lock不像lock_guard只能在析构时才释放锁,它可以随时释放锁,因此在wait时让unique_lock释放锁从语义上更加准确。
- 改写sync_queue.h
#ifndef SIMPLE_SYNC_QUEUE_H
#define SIMPLE_SYNC_QUEUE_H
#include <thread>
#include <condition_variable>
#include <mutex>
#include <list>
#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;
};
#endif // SIMPLE_SYNC_QUEUE_H
1.4 原子变量
#include <iostream> // std::cout
#include <atomic> // std::atomic, std::memory_order_relaxed
#include <thread> // std::thread
//std::atomic<int> foo = 0;//错误初始化
std::atomic<int> foo(0); // 准确初始化
void set_foo(int x)
{
foo.store(x,std::memory_order_relaxed); // 以非原子对象替换原子对象的值
}
void print_foo()
{
int x;
do {
x = foo.load(std::memory_order_relaxed); // 原子的获得对象的值
} while (x==0);
std::cout << "foo: " << x << '\n';
}
int main ()
{
std::thread first (print_foo);
std::thread second (set_foo,10);
first.join();
second.join();
std::cout << "main finish\n";
return 0;
}
1.5 call_once和once_flag
在多线程中,有一种场景是某个任务只需要执行一次,可以用C++11中的std::call_once函数配合std::once_flag来实现。多个线程同时调用某个函数,std::call_once可以保证多个线程对该函数只调用一次。
#include <iostream>
#include <thread>
#include <mutex>
std::once_flag flag1, flag2;
void simple_do_once()
{
std::cout << "simple_do_once\n" ;
std::call_once(flag1, [](){ std::cout << "Simple example: called once\n";
});
}
void may_throw_function(bool do_throw)
{
if (do_throw) {
std::cout << "throw: call_once will retry\n"; //
throw std::exception();
}
std::cout << "Didn't throw, call_once will not attempt again\n"; // 保证一次
}
void do_once(bool do_throw)
{
try {
std::call_once(flag2, may_throw_function, do_throw);
}
catch (...) {
}
}
int main()
{
std::thread st1(simple_do_once);
std::thread st2(simple_do_once);
std::thread st3(simple_do_once);
std::thread st4(simple_do_once);
st1.join();
st2.join();
st3.join();
st4.join();
std::thread t1(do_once, false);
std::thread t2(do_once, false);
std::thread t3(do_once, false);
std::thread t4(do_once, true);
t1.join();
t2.join();
t3.join();
t4.join();
}
1.6 异步操作
1.6.1 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(5)); // 用来测试异步延迟的影响
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 << "Hello World" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(5));
}
int main()
{
// std::future<int> result = std::async(find_result_to_add);
// std::future<decltype (find_result_to_add())> result = std::async(find_result_to_add);
auto result = std::async(find_result_to_add); // 推荐的写法
do_other_things();
std::cout << "result: " << result.get() << std::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; // 延迟是否有影响?
// std::cout << "main finish" << 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
1.6.2 std::packaged_task
如果说 std::async 和 std::feature 还是分开看的关系的话,那么std::packaged_task就是将任务和feature绑定在一起的模板,是一种封装对任务的封装。
可以通过std::packaged_task对象获取任务相关联的feature,调用get_future()方法可以获得std::packaged_task对象绑定的函数的返回值类型的future。std::packaged_task的模板参数是函数签
名。
//1-6-package_task
#include <iostream>
#include <future>
using namespace std;
int add(int a, int b, int c)
{
return a + b + c;
}
void do_other_things()
{
std::cout << "Hello World" << std::endl;
}
int main()
{
std::packaged_task<int(int, int, int)> task(add); // <int(int,int)> 函数签名
do_other_things();
std::future<int> result = task.get_future();
task(1, 1, 2); //必须要让任务执行,否则在get()获取future的值时会一直阻塞
std::cout << result.get() << std::endl;
return 0;
}
1.6.3 std::promise
从字面意思上理解promise代表一个承诺。promise比std::packaged_task抽象层次低。
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 print2(std::promise<int>& p)
{
p.set_value(1);
}
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 " << result.get() << std::endl;
t.join();
std::promise<int> promise2;
std::future<int> result2 = promise2.get_future();
std::thread t2(print2, std::ref(promise2));
do_some_other_things();
std::cout << "result2 " << result2.get() << std::endl;
t2.join();
return 0;
}
由此可以看出在promise创建好的时候future也已经创建好了。线程在创建promise的同时会获得一个future,然后将promise传递给设置他的线程,当前线程则持有future,以便随时检查是否可以取值。
future的表现为期望,当前线程持有future时,期望从future获取到想要的结果和返回,可以把future当做异步函数的返回值。而promise是一个承诺,当线程创建了promise对象后,这个promise对象向线程承诺他必定会被人设置一个值,和promise相关联的future就是获取其返回的手段。
2 function和bind用法
在设计回调函数的时候,无可避免地会接触到可回调对象。在C++11中,提供了std::function和
std::bind两个方法来对可回调对象进行统一和封装。
C++语言中有几种可调用对象:函数、函数指针、lambda表达式、bind创建的对象以及重载了函数调用运算符的类。
和其他对象一样,可调用对象也有类型。例如,每个lambda有它自己唯一的(未命名)类类型;函数及函数指针的类型则由其返回值类型和实参类型决定。
2.1 function的用法
- 保存普通函数
void printA(int a) { cout << a << endl; } std::function<void(int a)> func; func = printA; func(2); // 输出2
- 保存lambda表达式
std::function<void()> func_1 = [](){cout << "hello world" << endl;}; func_1(); //hello world
- 保存成员函数
class Foo{ Foo(int num) : num_(num){} void print_add(int i) const {cout << num_ + i << endl;} int num_; }; //保存成员函数 std::function<void(const Foo&,int)> f_add_display = &Foo::print_add; Foo foo(2); f_add_display(foo,1); // 3
2.2 bind 用法
可将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适
应”原对象的参数列表。
调用bind的一般形式:auto newCallable = bind(callable,arg_list);
其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。即,当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
arg_list中的参数可能包含形如n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置:1为newCallable的第一个参数,_2为第二个参数,以此类推。
#include <iostream>
#include <functional>
using namespace std;
class A
{
public:
void fun_3(int k,int m)
{
cout << "fun_3 a = " << a<< endl;
cout<<"print: k="<<k<<",m="<<m<<endl;
}
int a;
};
void fun_1(int x,int y,int z)
{
cout<<"print: x=" <<x<<",y="<< y << ",z=" <<z<<endl;
}
void fun_2(int &a,int &b)
{
a++;
b++;
cout<<"print: a=" <<a<<",b="<<b<<endl;
}
int main()
{
//f1的类型为 function<void(int, int, int)>
auto f1 = std::bind(fun_1, 1, 2, 3); // 表示绑定函数fun_1的参数值分别为: 1 2 3
f1(); //执行绑定函数fun_1,参数为上一行。结果:print: x=1,y=2,z=3
auto f11 = std::bind(fun_1, 10, 20, 30); //表示绑定函数 fun_1的参数值为: 10 20 30
f11();
auto f2 = std::bind(fun_1, placeholders::_1,placeholders::_2, 3); // 占位符
//表示绑定函数fun_1的第三个参数为3,而第一,二个参数分别由调用f2时的第一,二个参数指定
f2(1,2);//print: x=1,y=2,z=3
f2(10,21,30); // 10 21 3 3被绑定了
auto f3 = std::bind(fun_1,placeholders::_2,placeholders::_1,3);
//表示绑定第三个参数为3,而第一,二个参数分别由调用 f3 的第二,一个参数指定
f3(1,2);//print: x=2,y=1,z=3
int m = 2;
int n = 3;
auto f4 = std::bind(fun_2, placeholders::_1, n);
//表示绑定fun_2的第二个参数为n, fun_2的第一个参数由调用f4的第一个参数(_1)指定。
f4(m); //print: a=3,b=4
cout<<"m="<<m<<endl;//m=3 说明:bind对于不事先绑定的参数,是通过引用传递的,如m
cout<<"n="<<n<<endl;//n=3 说明:bind对于预先绑定的函数参数是通过值传递的,如n
A a;
a.a = 10;
//f5的类型为 function<void(int, int)>
auto f5 = std::bind(&A::fun_3, &a,placeholders::_1,placeholders::_2); //使用auto关键字
f5(10,20);//调用a.fun_3(10,20),print: k=10,m=20
A a2;
a2.a = 20;
//f5的类型为 function<void(int, int)>
auto f6 = std::bind(&A::fun_3, &a2,placeholders::_1,placeholders::_2); //使用auto关键字
f6(10,20);//
std::function<void(int,int)> fc = std::bind(&A::fun_3,a,std::placeholders::_1,std::placeholders::_2);
fc(10,20); //调用a.fun_3(10,20) print: k=10,m=20
fc = std::bind(&A::fun_3, a2,std::placeholders::_1,std::placeholders::_2);
return 0;
}