同步并发操作(下)
4.3 限定等待时间
- 之前介绍过的所有阻塞调用,将会阻塞一段不确定的时间,将线程挂起直到等待的事件发生
- 一些情况下要限制一下线程等待的时间
- 发送一些类似“我还存活”的信息,无论是对交互式用户,或是其他进程,亦或当用户放弃等待,你可以按下“取消”键直接终止等待
- 两种超时方式:
- 第一种方式,需要指定一段时间(例如,30毫秒) ——称时延
- 第二种方式,就是指定一个时间点 ——称绝对
- 处理持续时间的变量以“_for”作为后缀,处理绝对时间的变量以"_until"作为后缀
4.3.1 时钟
- 时钟节拍:若每个节拍为2.5s,则 std::ratio<5, 2>
- 当时钟节拍均匀分布(无论是否与周期匹配),并且不可调整,这种时钟就称为稳定时钟;
- std::chrono::system_clock是不稳定的,因为时钟是可调的,代表了系统时钟的“实际时间”,并且提供了函数可将时间点转化为time_t类型的;
- 稳定时钟对于计时非常重要,因此c++给出了稳定时钟 std::chrono::steady_clock
- std::chrono::high_resolution_clock 可能是标准库中提供的具有最小节拍周期(因此具有最高的精度[分辨率])的时钟
4.3.2 时延
-
std::chrono::duration<>函数模板能够对时延进行处理(线程库使用到的所有C++时间处理工具,都在std::chrono命名空间内)
-
第一个模板参数是一个类型表示——两个数相除后所的数的类型;
-
第二个模板参数是制定部分,表示每一个单元所用秒数;
如:std::chrono::duration<short, std::ratio<60, 1>> 代表单位是分钟 -
在chrono空间内有已定义好的:nanoseconds[纳秒] , microseconds[微秒] , milliseconds[毫秒] , seconds[秒] , minutes[分]和hours[时]
-
显示转换可以由std::chrono::duration_cast<>
std::chrono::milliseconds ms(54832);
std::chrono::seconds s = std::chrono::duration_cast<std::chrono::seconds>(ms);
这里的结果就是截断的,而不是进行了舍入,所以s最后的值将为54;
std::future<int> f=std::async(some_task);
if(f.wait_for(std::chrono::milliseconds(35))==std::future_status::ready)
do_something_with(f.get());
- 等待函数会返回一个状态值,来表示等待是超时,还是继续等待
- 当函数等待超时时,会返回std::future_status::timeout
- 当“future”状态改变,函数会返回std::future_status::ready
4.3.3 时间点
- 时钟的时间点可以用std::chrono::time_point<>的类型模板实例来表示
第一个参数用来指定所要使用的时钟
第二个函数参数用来表示时间的计量单位(特化的std::chrono::duration<>) - 时钟可能共享一个时间戳,或具有独立的时间戳。当两个时钟共享一个时间戳时,其中一个time_point类型可以与另一个时钟类型中的time_point相关联
- 例如: std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>
- std::chrono::hight_resolution_clock::now() + std::chrono::nanoseconds(500) 将得到500纳秒后的时间
- 代码块的计时
auto start = std::chrono::system_time::now();
// do some thing
auto stop = std:: chrono::system_time::now();
cout << "run time is "" << std::chrono::duration<double,std::chrono::seconds>(stop-start).count() << endl;
#include <condition_variable>
#include <mutex>
#include <chrono>
std::condition_variable cv;
bool done;
std::mutex m;
bool wait_loop()
{
auto const timeout= std::chrono::steady_clock::now()+
std::chrono::milliseconds(500);
std::unique_lock<std::mutex> lk(m);
while(!done)
{
if(cv.wait_until(lk,timeout)==std::cv_status::timeout)
break;
}
return done;
}
4.4 使用同步操作简化代码
- 比起在多个线程间直接共享数据,每个任务拥有自己的数据会应该会更好,并且结果可以对其他线程进行广播,这就需要使用future来完成了
4.4.1 使用future的函数化编程(functional programming)
- 术语函数化编程(functional programming)引用于一种编程方式,这种方式中的函数结果只依赖于传入函数的参数,并不依赖外部状态
- 大多数函数都是纯粹的(共享数据没有被修改,那么就不存在条件竞争,并且没有必要使用互斥量去保护共享数据)
- 一个future对象可以在线程间互相传递,并允许其中一个计算结果依赖于另外一个的结果,而非对共享数据的显式访问
快速排序 FP模式并行版
template<typename T>
std::list<T> parallel_quick_sort(std::list<T> input) // 输入一个链表list,返回一个链表
{
if(input.empty())
{
return input;
}
std::list<T> result; // 构建一个链表list result存储结果
result.splice(result.begin(),input,input.begin()); // 选择input链表的第一个元素作为“中间”值
T const& pivot=*result.begin(); // 避免多次拷贝,使用引用
auto divide_point=std::partition(input.begin(),input.end(),
[&](T const& t){return t<pivot;}); //利用partition将其分为大于中间值,和小于中间值的
std::list<T> lower_part;
lower_part.splice(lower_part.end(),input,input.begin(),
divide_point);
std::future<std::list<T> > new_lower( // 创建一个新的线程去处理小于中间值部分的数据
std::async(¶llel_quick_sort<T>,std::move(lower_part)));
auto new_higher(
parallel_quick_sort(std::move(input))); // 本线程中处理大于中间值的部分
result.splice(result.end(),new_higher); // 将大于的部分连接到resul list中
result.splice(result.begin(),new_lower.get()); // 将小于中间值的部分连接到result list 中
return result;
}
- 比起使用std::async(),你可以写一个spawn_task()函数对std::packaged_task和std::thread做简单的包装
template<typename F,typename A>
std::future<std::result_of<F(A&&)>::type>
spawn_task(F&& f,A&& a)
{
typedef std::result_of<F(A&&)>::type result_type;
std::packaged_task<result_type(A&&)>
task(std::move(f)));
std::future<result_type> res(task.get_future());
std::thread t(std::move(task),std::move(a));
t.detach();
return res;
}