翻译自:《C Concurrency In Action 2 Ed》
形如std::this_thread::sleep_for(std::chrono::duration)与std::this_thread::sleep_until(std::chrono::time_point)的线程休眠只是超时处理的一种形式,超时可以配合条件变量std::condition_variable和期望值std::future一起使用。超时甚至可以在尝试获取互斥锁(当互斥量支持超时时)使用。std::mutex和std::recursive_mutex都不支持超时锁,但是std::timed_mutex和std::recursive_timed_mutex支持。这两种类型拥有try_lock_for()和try_lock_until()成员函数,可以在一段时间内尝试获取锁或在指定时间点前获取互斥锁。
下表展示了C++标准库中支持超时的函数。参数列表中的延时duration必须是std::duration<type,std::ratio<s,r>>的实例,且时间点time_point必须是std::time_point<>的实例。
类/名字空间 | 函数/方法 | 返回值 |
---|---|---|
std::this_thread namespace | sleep_for(duration) sleep_until(time_point) | N/A |
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,predicate) wait_until(lock,time_point,predicate) | bool—the return value of the predicate when woken唤醒 | |
std::timed_mutex, std::recursive_timed_mutex或 std::shared_timed_mutex | try_lock_for(duration) try_lock_until(time_point) | bool—true if the lock was acquired获得,false otherwise |
std::shared_timed_mutex | try_lock_shared_for(duration) try_lock_shared_until(time_point) | bool—true if the lock was acquired获得,false otherwise |
std::unique_lock<TimedLockable> unique_lock(lockable,duration) unique_lock(lockable,time_point) | N/A—owns_lock()onthenewly constructedobjectreturnstrueifthelock wasacquired获得,falseotherwise | |
try_lock_for(duration) try_lock_until(time_point) | bool—trueifthelockwasacquired,false otherwise | |
std::shared_lock<SharedTimedLockable> shared_lock(lockable,duration) | N/A—owns_lock()onthenewly constructedobjectreturnstrueifthelock wasacquired,falseotherwise | |
try_lock_for(duration) | bool—trueifthelockwasacquired,false otherwise | |
std::future<ValueType>或 std::shared_future<Value-Type> | wait_for(duration) wait_until(time_point) | std::future_status::timeoutifthewaittime dout, std::future_status::readyifthefutureis ready,或 std::future_status::deferredifthefuture holdsadeferredfunctionthathasn'tyet started |
表中所有用法示例在https://en.cppreference.com/w/cpp/thread/sleep_for中都有例子,这真是一个极好的网站!:
1、std::this_thread::sleep_for
签名:
template< class Rep, class Period >
void sleep_for( const std::chrono::duration<Rep, Period>& sleep_duration );
阻塞当前线程的执行至少sleep_duration时间段。
由于线程调度scheduling或资源竞争resource contention导致的延迟delay,该函数可能会阻塞比sleep_duration更长的时间。
标准推荐使用一个稳定的时钟来处理duration。如果实现使用系统时钟代替,则等待时间可能会对时钟调整敏感。be sensitive to clock adjustments。
#include <iostream>
#include <chrono>
#include <thread>
int main()
{
using namespace std::chrono_literals;
std::cout << "Hello waiter\n" << std::flush;
auto start = std::chrono::high_resolution_clock::now(); //high_resolution_clock时钟是一个稳定时钟steady clock
std::this_thread::sleep_for(2000ms);
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::milli> elapsed = end-start;
std::cout << "Waited " << elapsed.count() << " ms\n";
}
Possible output:
Hello waiter
Waited 2000.12 ms
2、std::condition_variable::wait_for(lock,duration,predicate)
https://en.cppreference.com/w/cpp/thread/condition_variable
首先复习一下条件变量:std::condition_variable定义在<condition_variable>头文件中,std::condition_variable类是用来同时阻塞一个或多个线程的同步原语,直到另一个线程修改共享变量,并通知std::condition_variable。
尝试修改std::condition_variable的生产者线程必须:
1、获得一个std::mutex(典型如std::lock_guard、std::unique_lock构造即上锁)互斥量,这是对要修改的共享数据上写锁;
2、在持有锁的前提下执行共享数据的修改;
3、在std::condition_variable条件变量上执行notify_one()或notify_all(),条件变量通知的操作不需要持有锁,也就是说共享变量修改好后就可以解锁,然后再唤醒wait()等待的线程检查共享数据。。
既使共享变量是原子的(也就是共享变量自身的方法已经是同步过的),也必须在单另加锁的前提下被修改,为了正确地发布修改到等待线程(这句话的意思是对共享数据总是要加写锁,从而避免重叠写、部分读的数据竞争)。
同时,任何想要在std::condition_variable条件变量上等待的消费者线程必须:
1、获得一个std::unique_lock<std::mutex>互斥量,与修改共享数据的生产者线程中所用的保护共享数据的互斥量是同一互斥量。
2、可以是:
i、检查条件(也就是"cv.wait(lk, []{return ready;});"函数体部分),万一共享数据已经被更新过,或条件变量已经被通知过notified(被通知前肯定也已经更新了共享数据)。
ii、执行wait()、wait_for()或wait_until()。wait操作原子地释放互斥量并挂起所在线程的执行(休眠)。
iii、当条件变量被通知,或者超时期满(timeout expire,超时期满cv.wait_for(lk,dur,[&]{return bready;})唤醒后会主动调用检查函数),或者发生伪唤醒(就是某个休眠的线程没有被期望唤醒它的线程锁唤醒,参考鄙文https://blog.youkuaiyun.com/HayPinF/article/details/113526011),线程将被唤醒,同时自动获得互斥量(也就是唤醒后持锁继续执行)。
std::unique_lock<std::mutex> lk(mut);
auto dur(std::chrono::seconds(1));
if(!bready){
bool bpredicate=cv.wait_for(lk,dur,[&]{return bready;}); //传递捕获
if(bpredicate)std::cout<<"子线程"<<id<<"消费bready\n";
else std::cout<<"子线程"<<id<<"wait_for(bready)超时!\n";
}
或者:
i、对wait()、wait_for()和wait_until()使用断言的重载,从而自主地小心处理上面三个步骤。
std::condition_variable条件变量只与std::unique_lock<std::mutex>搭配使用(也就是std::unique_lock<std::mutex> lk(mut); cv2.wait(lk,[&]{return bprocessed;}););这个约束使得在一些平台上能发挥最大效果(因为std::unique_lock<>比std::lock_guard<>更灵活,同为构造上锁但std::unique_lock<>可以手动解锁,从而在修改完共享数据后手动解锁lk.unlock(),然后在无锁状态下执行cv.notify_one()通知唤醒等待线程)。
std::condition_variable_any是可以与任何基本可锁对象BasicLockable object搭配使用的条件变量,比如std::shared_lock。
条件变量的wait()、wait_for()、wait_until()、notify_one()和notify_all()成员方法允许并发调用(典型如多个线程休眠等待同一个条件变量。notify_one()通知唤醒休眠线程,此时该条件变量下的休眠线程队列中最早休眠的那个线程被唤醒。至于并发调用notify_one()没想到有什么特别用途,还有并发调用notify_all()岂不是只有第一个调用有用而剩下的调用没效果?)
std::condition_variable类是一个标准设计类型StandardLayoutType。它不是可拷贝构造的,不是可移动构造的,不是可拷贝赋值的,也不是可移动赋值的。
3、