跟我学C++中级篇——sleep和wait的不同

236 篇文章 96 订阅
12 篇文章 0 订阅

一、线(进)程的等待

在实际的开发场景中,经常遇到一种情况,需要某一线程进行等待一段时间再工作。这种等待可能是一次性的,也可能是定时的;也有可能是间断但时间可等可不等的。举一个简单例子,线程想每隔一分钟去查询一下当前的状态从而决定是否工作。还有某些登陆的回退机制,可能都需要做时间的等待。这就需要开发者提供一套时间处理的方式。
在相关的库和API中,提供了两大类的这种等待的方法。一种是类Sleep的方式(包括usleep等),当前线程单纯睡(休)眠;另外一种是Wait方式,等待一段时间或者永久等待一种触发。下面就这两大类进行一下分析和说明。

二、sleep睡眠等待

一般来说,sleep睡眠方式的等待,在C++11以前C++中并未提供相关的方法,大多是使用相关的OS提供的接口。它们这些接口,虽然应用方式类似,但一些细节是不同的,比如函数的名字在Windows上叫Sleep而在Linux上是sleep。参数的单位也不同,在Windows中,它的单位是毫秒,而在类Linux环境中为秒。
sleep应用的优势在于简单方便,不需要什么额外库支持,一般系统都支持。
下面看例子:

#include <chrono>
#include <iostream>
#include <thread>
#include <unistd.h>

auto now() { return std::chrono::steady_clock::now(); }

auto awake_time()
{
    using std::chrono::operator""ms;
    return now() + 2000ms;
}
void testSleep(){
    sleep(3);
    std::cout<<"cur thread sleep 3 sec!"<<std::endl;
}

int main()
{
    auto t = std::thread(testSleep);
    std::cout << "Hello, waiter...\n" << std::flush;
    const auto start{now()};
    std::this_thread::sleep_until(awake_time());
    std::chrono::duration<double, std::milli> elapsed{now() - start};
    std::cout << "Waited " << elapsed.count() << " ms\n";
    sleep(3);
    std::cout<<"all end!"<<std::endl;
    t.join();

}

在C++11以后,C++标准库提供了sleep_for和sleep_until相关的函数,基本实现了类似的功能。sleep当前线程后,线程会交出CPU时间片,进入休眠,返回到等待状态。需要注意的,这种睡眠状态是无法被外界主动打破的,只能等待到指定的时间条件后才会触发。而且在这种睡眠中,所有的线程当前所拥有的资源仍然是持有的不会释放(一般关注点是资源的锁),所以这种睡眠其实是很重的。
在不同的环境下可能还有类似的函数接口,如Windows上的Pause()函数和Linux下的Delay()函数,他们的目的基本都差不多。
sleep这种等待应用的场景更广泛也更容易为开发者接受,但不适合于指定触发状态。所以就需要使用下面的wait方式。

三、wait函数等待

类似的这种wait函数的等待,一般是在同步状态中,用来处理多个线程间同步做某种工作即保持状态的一致性。一般多用于资源应用、IO操作和进程协调等。一般这种等待可以任意时刻触发,相关的机制中也提供了触发的函数类似于notify等。

#include <chrono>
#include <condition_variable>
#include <iostream>
#include <thread>

std::condition_variable cv;
std::mutex cv_m; // This mutex is used for three purposes:
                 // 1) to synchronize accesses to i
                 // 2) to synchronize accesses to std::cerr
                 // 3) for the condition variable cv
int i = 0;

void waits()
{
    std::unique_lock<std::mutex> lk(cv_m);
    std::cerr << "Waiting... \n";
    cv.wait(lk, []{ return i == 1; });
    std::cerr << "...finished waiting. i == 1\n";
}

void signals()
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    {
        std::lock_guard<std::mutex> lk(cv_m);
        std::cerr << "Notifying...\n";
    }
    cv.notify_all();

    std::this_thread::sleep_for(std::chrono::seconds(1));

    {
        std::lock_guard<std::mutex> lk(cv_m);
        i = 1;
        std::cerr << "Notifying again...\n";
    }
    cv.notify_all();
}

int main()
{
    std::thread t1(waits), t2(waits), t3(waits), t4(signals);
    t1.join();
    t2.join();
    t3.join();
    t4.join();
}

在类似这种等待中,特别是锁机制下,锁都是会被释放然后再进行等待。这也是在多线程中可以经常看到的,多个线程可以拿到锁但未触发时,都进入了阻塞状态。而触发后,又会全部醒来,准备工作,也就是开发者经常提到的“惊群”。
当然,它也提供了类似wait等待一段时间或持续一段时间的相关接口,如wait_for等,大家可以参看相关的资料说明。如果这样做的话,其实等于是揉和wait和sleep的功能,既可能等待一段时间自动触发,又可以在时间段内被主动触发。
如果更细节的处理,需要知道this_thread::yield()这个函数的机理,它会告诉CPU当前线程放弃执行权限了,不想运行了。但CPU是否会立刻切换到另外一个线程运行,则需要当前的状态和调度策略。

四、总结

其实在实际应用中,二者的应用大多是泾渭分明的。但真正的细节上的区别,还是需要认真的掌握。不然在开发具体的工程时,可能会一时疏忽导致不可预测的问题和错误。还是那句话,细节决定成败,但不能拘泥于细节。这个度,就需要开发者自己不断养成自己的判断。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值