一、条件变量等待条件成立,std::condition_variable
1.wait()条件成立时线程执行后续工作,否则线程原地待命进入睡眠状态
第一个参数是std::mutex类型
第二个参数是回调函数,返回值为true时wait()不阻塞,返回false进入阻塞状态
2.notify_one()触发一个调用wait()处于等待的线程去检验条件
3.notify_all()触发所有调用wait()处于等待的线程去检验条件
#include <iostream>
#include <thread>
#include <memory>
#include <mutex>
#include <chrono>
#include <queue>
std::mutex m;
std::condition_variable cond;
std::queue<int> que;
std::mutex mct;
void print(const int& i)
{
std::lock_guard<std::mutex> lk1(mct);
std::cout << i << "线程ID: " << std::this_thread::get_id() << std::endl;
}
void push()
{
for (int i =0;i< 1000;++i)
{
{
std::unique_lock<std::mutex> lk(m);
que.push(i);
}
cond.notify_one();//触发一个调用wait()处于等待的线程去检验条件
}
//cond.notify_all();//触发所有调用wait()处于等待的线程去检验条件
}
void pop()
{
while (true)
{
std::unique_lock<std::mutex> lk(m);
cond.wait(lk, [] { return !que.empty(); });//调用wait检验条件是否成立
int i = que.front();
que.pop();
lk.unlock();
print(i);
}
}
int main()
{
std::thread t2(pop);
std::thread t3(pop);
std::thread t4(pop);
//休眠1秒钟,让pop线程进入wait状态,开启push线程唤醒pop线程
std::this_thread::sleep_for(std::chrono::seconds(1));
std::thread t1(push);
t1.join();
t2.join();
t3.join();
t4.join();
}
4.实现一个多线程安全的队列
#include <mutex>
#include <condition_variable>
#include <queue>
#include <memory>
template<typename T>
class threadsafe_queue
{
private:
mutable std::mutex mut;
std::queue<T> data_queue;
std::condition_variable data_cond;
public:
threadsafe_queue()
{}
threadsafe_queue(threadsafe_queue const& other)
{
std::lock_guard<std::mutex> lk(other.mut);
data_queue=other.data_queue;
}
void push(T new_value)
{
std::lock_guard<std::mutex> lk(mut);
data_queue.push(new_value);
data_cond.notify_one();
}
void wait_and_pop(T& value)
{
std::unique_lock<std::mutex> lk(mut);
data_cond.wait(lk,[this]{return !data_queue.empty();});
value=data_queue.front();
data_queue.pop();
}
std::shared_ptr<T> wait_and_pop()
{
std::unique_lock<std::mutex> lk(mut);
data_cond.wait(lk,[this]{return !data_queue.empty();});
std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));
data_queue.pop();
return res;
}
bool try_pop(T& value)
{
std::lock_guard<std::mutex> lk(mut);
if(data_queue.empty)
return false;
value=data_queue.front();
data_queue.pop();
return true;
}
std::shared_ptr<T> try_pop()
{
std::lock_guard<std::mutex> lk(mut);
if(data_queue.empty())
return std::shared_ptr<T>();
std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));
data_queue.pop();
return res;
}
bool empty() const
{
std::lock_guard<std::mutex> lk(mut);
return data_queue.empty();
}
};
int main()
{}
二.使用std::future等待一次性事件发生
1.std::async()的返回future
future::get() 将等待任务函数返回合法结果并取得它
std::launch::async 启用异步求值
std::launch::deferred 启用惰性求值
#include <future>
#include <iostream>
#include <mutex >
std::mutex m;
int answer()
{
std::cout << "answer threadID:" << std::this_thread::get_id() << std::endl;
return 3;
}
void do_other_stuff()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
class X {
public:
X() {};
public:
int answer1(int num)
{
std::cout << "X::answer1(int num)\tthreadID:" << std::this_thread::get_id() << std::endl;
return num;
};
int answer2(int& num)
{
std::cout << "X::answer2(int& num)\tthreadID:" << std::this_thread::get_id() << std::endl;
return num;
};
int answer3(int&& num)
{
std::cout << "X::answer3(int&& num)\tthreadID:" << std::this_thread::get_id() << std::endl;
return num;
};
void bar(const std::string& str) {
std::lock_guard<std::mutex> lk(m);
std::cout << str << '\n';
}
};
class Y {
public:
double operator()(double var) { return var; };
};
int main()
{
std::cout << "main threadID:" << std::this_thread::get_id() << std::endl;
/*
调用std::future的成员函数get()获取异步任务函数的返回值
若std::async的任务函数没有运行结束,get()进入阻塞状态,等待std::async任务完成
get()只能调用一次,多次调用会发生异常
*/
std::future<int> the_answer = std::async(answer);
//执行其它业务代码
do_other_stuff();
std::cout << "The answer is " << the_answer.get() << std::endl;
/*
传入类成员函数做为std::async的任务函数,第二个参数可以是类对象的指针
也可以是类对象的副本,也可以是由std::ref包装的对象
*/
X x;
//指向类对象的指针
std::future<int> the_X1 = std::async(&X::answer1, &x, 1);
do_other_stuff();
std::cout << the_X1.get() << std::endl;
//类对象的副本
int nVar = 2;
std::future<int> the_X2 = std::async(&X::answer2, x, std::ref(nVar));
do_other_stuff();
std::cout << the_X2.get() << std::endl;
//std::ref包装的类对象
std::future<int> the_X3 = std::async(&X::answer3, std::ref(x), answer());
do_other_stuff();
std::cout << the_X3.get() << std::endl;
/*
传入可调用对象做为std::async的任务函数
*/
Y y;
std::future<double> the_answer1 = std::async(y, 5.5);
do_other_stuff();
std::cout << "The answer1 is " << the_answer1.get() << std::endl;
std::future<double> the_answer2 = std::async(std::ref(y), 6.6);
do_other_stuff();
std::cout << "The answer2 is " << the_answer2.get() << std::endl;
std::future<double> the_answer3 = std::async(Y(), 7.7);
do_other_stuff();
std::cout << "The answer3 is " << the_answer3.get() << std::endl;
// 以 deferred 策略调用 x.bar("world!")
// 调用 a2.get() 或 a2.wait() 时打印 "world!"
auto a2 = std::async(std::launch::deferred, &X::bar, x, "world!");
// 以 async 策略调用 X()(43) :
// 同时打印 "43"
auto a3 = std::async(std::launch::async, Y(), 8.8);
a2.wait(); // 打印 "world!"
std::cout << a3.get() << '\n'; // 打印 "53"
std::cin.get();
}
2.std::packaged_task关联future实例和任务
可调用操作符执行后,future即准备就绪,调用get就能取得结果
#include <deque>
#include <mutex>
#include <future>
#include <thread>
#include <utility>
#include <chrono>
#include <iostream>
#include <cmath>
#include <functional>
// unique function to avoid disambiguating the std::pow overload set
int f(int x, int y) { return std::pow(x, y); }
void task_lambda()
{
std::packaged_task<int(int, int)> task([](int a, int b) {
return std::pow(a, b);
});
std::future<int> result = task.get_future();
task(2, 9);
std::cout << "task_lambda:\t" << result.get() << '\n';
}
void task_bind()
{
std::packaged_task<int()> task(std::bind(f, 2, 11));
std::future<int> result = task.get_future();
task();
std::cout << "task_bind:\t" << result.get() << '\n';
}
void task_thread()
{
std::packaged_task<int(int, int)> task(f);
std::future<int> result = task.get_future();
std::thread task_td(std::move(task), 2, 10);
task_td.join();
std::cout << "task_thread:\t" << result.get() << '\n';
}
std::mutex m;
std::deque<std::packaged_task<int()> > tasks;
void gui_thread()
{
for (int i =0;i < 1;++i)//换成while(true)
{
std::packaged_task<int()> task;
{
std::lock_guard<std::mutex> lk(m);
if (tasks.empty())
continue;
task = std::move(tasks.front());
tasks.pop_front();
}
task();
}
}
template<typename Func>
std::future<int> post_task_for_gui_thread(Func f)
{
std::packaged_task<int()> task(f);
std::future<int> res = task.get_future();
std::lock_guard<std::mutex> lk(m);
tasks.push_back(std::move(task));
return res;
}
void task_gui()
{
//带参数用std::bind
std::future<int> ret = post_task_for_gui_thread([]() { return 100; });
std::thread gui(gui_thread);
std::cout << ret.get() << std::endl;
gui.join();
}
int main()
{
task_lambda();
task_bind();
task_thread();
task_gui();
return 0;
}
3.std::promise关联future
set_value()调用后,future即准备就绪,调用get就能取得结果
#include <vector>
#include <thread>
#include <future>
#include <numeric>
#include <iostream>
#include <chrono>
void accumulate(std::vector<int>::iterator first,
std::vector<int>::iterator last,
std::promise<int> accumulate_promise)
{
int sum = std::accumulate(first, last, 0);
accumulate_promise.set_value(sum); // 提醒 future
}
void do_work(std::promise<void> barrier)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
barrier.set_value();
}
int main()
{
// 演示用 promise<int> 在线程间传递结果。
std::vector<int> numbers = { 1, 2, 3, 4, 5, 6 };
std::promise<int> accumulate_promise;
std::future<int> accumulate_future = accumulate_promise.get_future();
std::thread work_thread(accumulate, numbers.begin(), numbers.end(),
std::move(accumulate_promise));
// future::get() 将等待直至该 future 拥有合法结果并取得它
// 无需在 get() 前调用 wait()
//accumulate_future.wait(); // 等待结果
std::cout << "result=" << accumulate_future.get() << '\n';
work_thread.join(); // wait for thread completion
// 演示用 promise<void> 在线程间对状态发信号
std::promise<void> barrier;
std::future<void> barrier_future = barrier.get_future();
std::thread new_work_thread(do_work, std::move(barrier));
barrier_future.wait();
new_work_thread.join();
}
4.std::shared_future多个线程一起等待
类模板 std::shared_future 提供访问异步操作结果的机制,类似 std::future ,除了允许多个线程等候同一共享状态。不同于仅可移动的 std::future (故只有一个实例能指代任何特定的异步结果),std::shared_future 可复制而且多个 shared_future 对象能指代同一共享状态。
若每个线程通过其自身的 shared_future 对象副本访问,则从多个线程访问同一共享状态是安全的。
#include <iostream>
#include <future>
#include <chrono>
int main()
{
std::promise<void> ready_promise, t1_ready_promise, t2_ready_promise;
std::shared_future<void> ready_future(ready_promise.get_future());
std::chrono::time_point<std::chrono::high_resolution_clock> start;
auto fun1 = [&, ready_future]() -> std::chrono::duration<double, std::milli>
{
t1_ready_promise.set_value();
ready_future.wait(); // 等待来自 main() 的信号
return std::chrono::high_resolution_clock::now() - start;
};
auto fun2 = [&, ready_future]() -> std::chrono::duration<double, std::milli>
{
t2_ready_promise.set_value();
ready_future.wait(); // 等待来自 main() 的信号
return std::chrono::high_resolution_clock::now() - start;
};
auto result1 = std::async(std::launch::async, fun1);
auto result2 = std::async(std::launch::async, fun2);
// 等待线程变为就绪
t1_ready_promise.get_future().wait();
t2_ready_promise.get_future().wait();
// 线程已就绪,开始时钟
start = std::chrono::high_resolution_clock::now();
// 向线程发信使之运行
ready_promise.set_value();
std::cout << "Thread 1 received the signal "
<< result1.get().count() << " ms after start\n"
<< "Thread 2 received the signal "
<< result2.get().count() << " ms after start\n";
}
三.限时等待
1.时钟类
若时钟的计时速率恒定(无论速率是否与计时单元相符)且无法调整,则称之为恒稳时钟。时钟类具有静态数据成员is_ steady,该值在恒稳时钟内为true否则为false。
1.std::chrono::system_clock系统时钟
通常,std::chrono::system_clock 类不是恒稳时钟,因为它可调整。即便这种调整自动发生,作用是消除本地系统时钟的偏差,依然可能导致:调用两次 now(),后来返回的时间值甚至早于前一个,结果违反恒定速率的规定。我们马上会了解到,恒稳时钟对于超时时限的计算至关重要。
2.std::chrono::steady_ clock恒稳时钟
决不会调整的单调时钟
#include <iostream>
#include <vector>
#include <numeric>
#include <chrono>
volatile int sink;
int main()
{
for (auto size = 1ull; size < 1000000000ull; size *= 100) {
// 记录开始时间
auto start = std::chrono::steady_clock::now();
// 做一些工作
std::vector<int> v(size, 42);
sink = std::accumulate(v.begin(), v.end(), 0u); // 确保其副效应
// 记录结束时间
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double> diff = end-start;
std::cout << "Time to fill and iterate a vector of "
<< size << " ints : " << diff.count() << " s\n";
}
}
2.std::chrono::duration时间间隔类
由秒转毫秒无精度损失,可正常转换;
由秒转分有精度损失,正常转换不可行,需要通过显示转换完成;
预设模板:
类型 定义
std::chrono::nanoseconds duration</至少 64 位的有符号整数类型/, std::nano>
std::chrono::microseconds duration</至少 55 位的有符号整数类型/, std::micro>
std::chrono::milliseconds duration</至少 45 位的有符号整数类型/, std::milli>
std::chrono::seconds duration</至少 35 位的有符号整数类型/>
std::chrono::minutes duration</至少 29 位的有符号整数类型/, std::ratio<60>>
std::chrono::hours duration</至少 23 位的有符号整数类型/, std::ratio<3600>>
std::chrono::days (C++20 起) duration</至少 25 位的有符号整数类型/, std::ratio<86400>>
std::chrono::weeks (C++20 起) duration</至少 22 位的有符号整数类型/, std::ratio<604800>>
std::chrono::months (C++20 起) duration</至少 20 位的有符号整数类型/, std::ratio<2629746>>
std::chrono::years (C++20 起) duration</至少 17 位的有符号整数类型/, std::ratio<31556952>>
#include <iostream>
#include <chrono>
constexpr auto year = 31556952ll; // 格里高利历年的平均秒数
int main()
{
using shakes = std::chrono::duration<int, std::ratio<1, 100000000>>;
using jiffies = std::chrono::duration<int, std::centi>;
using microfortnights = std::chrono::duration<float, std::ratio<14*24*60*60, 1000000>>;
using nanocenturies = std::chrono::duration<float, std::ratio<100*year, 1000000000>>;
std::chrono::seconds sec(1);
std::cout << "1 second is:\n";
// 无精度损失的整数尺度转换:无转型
std::cout << std::chrono::microseconds(sec).count() << " microseconds\n"
<< shakes(sec).count() << " shakes\n"
<< jiffies(sec).count() << " jiffies\n";
// 有精度损失的整数尺度转换:需要转型
std::cout << std::chrono::duration_cast<std::chrono::minutes>(sec).count()
<< " minutes\n";
// 浮点尺度转换:无转型
std::cout << microfortnights(sec).count() << " microfortnights\n"
<< nanocenturies(sec).count() << " nanocenturies\n";
}
std::future的限时等待成员函数wait_for()
wait_for
阻塞当前线程,直到条件变量被唤醒,或到指定时限时长后
std::future的限时等待成员函数wait_for(),延迟超时的等待时长需要参考标准,它使用std::chrono::steady_ clock作为参考标准,只要代码等待了35毫秒,那现实中等待的时间就是35毫秒,即使期间系统时钟发生调整。
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());
3.std::chrono::time_point时间点
类模板 std::chrono::time_point 表示时间中的一个点。它被实现成如同存储一个 Duration 类型的自 Clock 的纪元起始开始的时间间隔的值。
#include <algorithm>
#include <iostream>
#include <iomanip>
#include <ctime>
#include <chrono>
void slow_motion()
{
static int a[] {1,2,3,4,5,6,7,8,9,10,11,12};
while (std::ranges::next_permutation(a).found)
{ } // 生成 12! 个排列
}
int main()
{
using namespace std::literals; // 允许用 24h 、 1ms 、 1s 代替对应的
// std::chrono::hours(24) 等待
const std::chrono::time_point<std::chrono::system_clock> now =
std::chrono::system_clock::now();
// “生产代码”可以简化为:
// const auto now = std::chrono::system_clock::now();
const std::time_t t_c = std::chrono::system_clock::to_time_t(now - 24h);
std::cout << "24 hours ago, the time was "
<< std::put_time(std::localtime(&t_c), "%F %T.\n") << std::flush;
const std::chrono::time_point<std::chrono::steady_clock> start =
std::chrono::steady_clock::now();
// “现实生活”的替用写法会是:
// const auto start = std::chrono::steady_clock::now();
slow_motion();
const auto end = std::chrono::steady_clock::now();
std::cout
<< "Slow calculations took "
<< std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() << "µs ≈ "
<< (end - start) / 1ms << "ms ≈ " // 几乎等价于以上形式,
<< (end - start) / 1s << "s.\n"; // 但分别使用毫秒和秒
}
std::condition_variable进行限时等待wait_until()
wait_until
阻塞当前线程,直到条件变量被唤醒,或直到抵达指定时间点
条件变量之上的等待函数理应接收一个断言,借此判定所等的条件是否成立。假如读者不提供断言,则该函数只能依据是否超越时限来决定要继续等待还是要结束,那么,我推荐效仿上例的方式,这样就限定了循环的总耗时。否则,若凭借条件变量等待,却未传入断言,那么我们需不断循环来处理伪唤醒,4.1.1节对此已作说明。假设循环中使用的是wait for(),那么,若在等待时间快消耗完时发生伪唤醒,而我们如果要再次等待,就得重新开始一次完整的迟延等待。这也许会不断重复,令等待变得漫无边际。
#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;
}