4.1 等待事件或其他条件
我们在配合做事情的时候,总是应该在前提条件完成的情况下,才会开始进行下一步操作,所以,我们可以选择以下几种方式:
1.实时查看前提条件的完成情况。这样可以及时的进行下一步任务,但是,这样的操作是一件十分浪费时间的事情,在这期间本来还可以做其他事情,现在由于实时关注前提条件的完成情况,不得不一直专注于这件事情。
2.每隔一定时间查看前提条件的完成情况。这样虽然我们可以在这些间断的时间用来做一些其他的事情,但是,这种查询方式不利于前提条件完成后及时进行下一步的任务。
3.让完成前条件的人,在完成该任务后及时通知你。这种方法可以避免中间的时间浪费,也可以及时的进行下一步的任务,是一件十分高效的策略。
故C++标注库中的条件变量等待条件正是第三种情况。
4.1.1 用条件变量等待条件
C++标准库中提供了两个条件变量的实现:std::condition_variable和std::condition_variable_any。前者std::condition_variable只能与std::mutex一起配合使用,但是后者std::condition_variable_any则可以与复核成为类似互斥元的最低标准的任何东西一起工作,但同时在大小、性能和操作系统资源方面有着额外的代价。下面展示std::condition_variable的使用。
#include<iostream> #include<condition_variable> #include<mutex> #include<chrono> #include<thread> using namespace std; mutex m; condition_variable cv; bool flg; void pre_print(){ cout<<"pre_print"<<endl; flg = true; cv.notify_one(); } void after_print(){ unique_lock<mutex> lg(m); cv.wait(lg, []{return flg;}); cout<<"after_print"<<endl; } int main(){ flg = false; thread t1(after_print), t2(pre_print); t1.detach(); //std::this_thread::sleep_for(std::chrono::seconds(3)); t2.join(); cout<<"finish_process"<<endl; return 0; }
4.1.2 使用条件变量建立一个线程安全队列
为queue书写一个安全的支持多线程并发的数据结构--safe_queue。
相关代码有时间再补
4.2 使用future等待一次事件
比如调用函数得到返回时,有时候这种事情只发生一次,在单线程中很容易解决,在多线程中我们要借助C++标准库中的future来解决,虽然future支持多线程通讯,但这种工具不支持同步访问,如果多线程需要同步访问同一个future应该使用互斥元等来解决。
在C++中关于future有两种:
1.future :future实例时仅有的一个指向其关联事件的实例。
2.shared_future :多个shared_future的实例可以指向同一个事件。
4.2.1 从后台任务中返回值
在使用多线程时,我们会想到thread,但是这种机制没有提供接收返回值的特性,所以我们现在要学习另一个用于多线程的函数模板,std::async,这个函数的使用方式和thread相似,但是结果返回一个std::future的对象,而不是一个thread对象让你等待,这个future对象最终持有该多线程函数的返回值,最终只需要在future对象上使用get函数即可得到函数返回值。例如
#include<thread> #include<future> #include<iostream> #include<vector> using namespace std; vector<int> add(){ vector<int> ans; for(int i = 0; i < 10; ++i) ans.push_back(i); return ans; } int main(){ future<vector<int>> res = async(add); vector<int> ans = res.get(); for(auto i: ans) cout<<i<<' '; cout<<endl; return 0; }
虽然上面的例子用单线程也能解决,但是上面的例子只是为说明该函数的使用方法。async也可以指定多线程调用的方法,async(std::launch::deferred表明在调用中一直延迟到使用future.wait()或者future.get()为止,但是async(std::launch::async)则表明必须在他自己的线程上运行。(注:根据课本中的几个字在wait()或者get()中运行,我感觉应该是在this_thread中运行,只是在wait或者get函数中再次启动该线程,如有不对希望高手指正)。
4.2.2 将任务与future相关联
C++标准库中有个类std::package_task<>可以将future与绑定到一个函数或者可调用对象上,当std::package_task<>被调用时,绑定的函数或者可调用对象也被调用,并且使future进入就绪状态,将返回值作为关联数据存储。当std::package_task<>作为可调用对象时被调用时,提供给函数调用运算符的参数被传给所包含的函数,并且将结果作为异步结果返回,存储在由get_future()获得的std::future中。使用书中示例如下
#include<deque> #include<mutex> #include<future> #include<thread> #include<utility> using namespace std; mutex m; deque<package_task<void()>> tasks; bool gui_shutdown_message_received(); void get_and_process_gui_message(); void gui_thread(){ while(!gui_shutdown_message_received()){ std::package_task<void()> task; { lock_guard<mutex> lg(m); if(tasks.empty()) continue; task = std::move(task.front()); task.pop_front(); } task(); } } std::thread gui_bg_thread(gui_thread); template<typename Func> std::future<void> post_task_for_gui_thread(Func f){ package_task<void()> task(f); future<void> res = task.get_future(); lock_guard<mutex> lg(m); tasks.push_back(std::move(task)); return res; }
4.2.3 生成std::promise
在我看来std::promise是一种用于线程间通讯的工具,std::promise<T>提供一种设置值(类型T)的方式,他可以在这之后通过相关联的std::future<T>对象进行读取。一对std::promise/std::future为这一设施提供了一种可能的机制,等待中线程可以阻塞future,同时提供数据的线程可以使用配对中的promise向,来设置相关的值并使future就绪。
同时你可以使用get_future()成员函数来获取与给定的std::promise相关的std::future对象。当设置完promise的值(使用set_value()成员函数),,future就会变为就绪,,并且可以用来获取锁存储的值。如果销毁std::promise时未设置值,则将存入一个异常。示例如下
#include<iostream> #include<future> #include<condition_variable> #include<chrono> #include<thread> #include<mutex> using namespace std; promise<int> ps; mutex m; condition_variable cv; bool flg; void get_and_show_val(){ int t; unique_lock<mutex> ul(m); cv.wait(ul, [](){return flg;}); flg = false; future<int> ans = ps.get_future(); t = ans.get(); cout<<t<<endl; } void set_val(){ unique_lock<mutex> ul(m); ps.set_value(3); flg = true; cv.notify_one(); ul.unlock(); this_thread::sleep_for(chrono::seconds(3)); } int main(){ flg = false; thread t1(get_and_show_val), t2(set_val); t1.detach(); t2.join(); return 0; }
根据https://zh.cppreference.com/w/cpp/thread/promise 说明该promise只应该使用一次。
4.2.4 为future保存异常
当然也可以使用promise,但是在设置值的时候使用set_exception(),而不是set_value()。
std::promise<double> some_promise; try{ some_promise.set_val(calc_val()); } catch{ some_promise.set_exception(std::current_exception()); }
4.2.5 等待自多个线程
当多个线程访问std::furture对象而不进行额外同步时,就会出现数据竞争和未定义的行为,这是有意为之的,future模型统一异步结果的所有权,同时get()的单发性质是的这样的并发访问没有意义——只有一个线程可以获取值,因为在首次调用get()之后,就没有任何可获得的值留下了,但是这时我们可以使用std::shared_future使std::future交出所有权达到可以复制的效果,有几种方式可以使用shared_future
future<int> f; share_future<int> sf(f); //或者 // share_future<int> sf = f.share();
4.3 有时间的等待
关于超时有两种:
1.基于时间段:等待一个指定的时间长度。函数形式为_for
2.基于时间点(绝对超时):等到指定的时间点。函数形式为_until
4.3.1 时钟
时钟是时间信息的来源,具体来说,时钟提供以下四个不同部分信息的类。
1.现在时间
2.用来表示从时钟获取到的时间值的类型
3.时钟的节拍周期
4.时钟是否以均匀的速率进行计时,决定其是否为匀速(steady)时钟
对于具体的某个时间点类型是time_point成员的typedef
4.3.2 时间段
时间段是时间支持中的最简单部分,他们是由std::chrono::duration<>来进行处理的,第一个模板代表类型,第二个模板是分数,代表每个时间段表示多少秒,short存储的几分钟表示为std::chrono::duration<short, std::radio<60,1>>,以double存储的毫秒数则表示为std::chrono::duration<double, std::radio<1,1000>>表示1毫秒为1/1000秒
在无需截断的场合下,时间段之间的转换时隐式的(因此将小时转换为秒是可以的,但将秒转换为小时则不然),显式转换可以通过std::chrono::duration_cast<>来转换。
4.3.3 时间点
时间点是通过std::chrono::time_point<>类模板来管理的。
4.3.4 接收超时的函数
类/命名空间 | 函数 | 返回值 |
std::this_thread命名空间 |
sleep_for(duration) sleep_until(time_point) |
不可用 |
std::condition_variable或者 std::condition_variable_any |
wait_for(lock,duration) wait_until(lock, time_point) |
std::cv_status::timeout或 std::cv_status::no_timeout |
wait_for(lock, duration, predication) wait_unitl(lock, time_point, predition) |
bool——当唤醒时predicate的返回值 | |
std::time_mutex或者 std::recursize_time_mutex |
try_lock_for(duration) try_lock_until(time_point) |
bool——true如果获得了锁,否则false |
std::unique_lock<TimedLockable> |
unique_lock(lockable, duration) unique_lokc(lockable, time_point) |
不可用——owns_lock()在新构造的对象上,如或获得了锁返回true,否则false |
try_lock_for(duration) try_lock_until(time_point) |
bool——如果获得了锁,否则返回false | |
std::future<ValueType>或者 std::shared_future<ValueType> |
wait_for(duration) wait_until(time_point) |
std::future_status::timeout如果等待超时, std::future_status::ready如果future就绪或 std::future_status::deferred如果future持有的延迟函数还没有开始 |