Home > Enterprise >  How to do mutex lock with a conditional variable in while loop?
How to do mutex lock with a conditional variable in while loop?

Time:11-16

I have two functions where one is to run serveRunForever and another is shutDown.

Server::runForever ()
{
while(!doStop_)
{
unique_lock<mutex> lck(stop);
//Do something, it should be looping in while loop
CV.wait(lck, [this]() {return doStop_;});
}

The above is not looping it start waiting in CV.wait().

Server::shutDown()
{
scoped_lock lck(stop);
doStop_ = true;
CV.notify_one();
}

the runForever is not looping in while, I would like to be in loop continuously untill shutdown called. Help me with this. I would really appreciate any leads and suggestions to this.

CodePudding user response:

Part 1

I did modifications to your program so that it works now. Mainly I had to make doStop_ = true; instead of doStop_ = false;, because according to logic of English program should stop when do-stop becomes true.

Also you have to place mutex guards the way I showed in my code below, i.e. first lock is outside while loop, and second flag-setting lock should be scoped outside notify call. In fact you actually don't need while loop if you use wait with predicate, as said in std docs.

Here is simulation of working process:

Try it online!

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

class Server {
public:
    void runForever();
    void shutDown();
private:
    std::condition_variable CV;
    std::mutex stop;
    bool doStop_ = false;
};

void Server::runForever() {
    std::unique_lock<std::mutex> lck(stop);
    while (!doStop_)
        CV.wait(lck, [this]() { return doStop_; });
}

void Server::shutDown() {
    {
        std::lock_guard<std::mutex> lck(stop);
        doStop_ = true;
    }
    CV.notify_one();
}

int main() {
    auto const time_begin = std::chrono::high_resolution_clock::now();
    auto TimeElapsed = [&]{
        return double(std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::high_resolution_clock::now() - time_begin).count()) / 1000;
    };

    Server server;
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    std::thread t0([&]{
        std::cout << "Server started at " << TimeElapsed() << " sec" << std::endl;
        server.runForever();
        std::cout << "Server stopped at " << TimeElapsed() << " sec" << std::endl;
    });
    std::this_thread::sleep_for(std::chrono::milliseconds(1500));
    server.shutDown();
    t0.join();
}

Output:

Server started at 0.2 sec
Server stopped at 1.7 sec

Part 2

If you need to do some computations inside while loop, not just wait, then conditional variable is not for you. You should use std::atomic bool instead, or just regular bool is almost enough too. Atomic allows for changed value of bool variable to be propagated immediately. See code below:

Try it online!

#include <atomic>
#include <thread>
#include <mutex>
#include <iostream>
#include <chrono>

class Server {
public:
    void runForever();
    void shutDown();
private:
    std::atomic<bool> doStop_ = false;
};

void Server::runForever() {
    while (!doStop_) {
        std::cout << "Doing computation..." << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }
}

void Server::shutDown() {
    doStop_ = true;
}

int main() {
    auto const time_begin = std::chrono::high_resolution_clock::now();
    auto TimeElapsed = [&]{
        return double(std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::high_resolution_clock::now() - time_begin).count()) / 1000;
    };

    Server server;
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    std::thread t0([&]{
        std::cout << "Server started at " << TimeElapsed() << " sec" << std::endl;
        server.runForever();
        std::cout << "Server stopped at " << TimeElapsed() << " sec" << std::endl;
    });
    std::this_thread::sleep_for(std::chrono::milliseconds(900));
    server.shutDown();
    t0.join();
}

Output:

Server started at 0.21 sec
Doing computation...
Doing computation...
Doing computation...
Doing computation...
Doing computation...
Server stopped at 1.21 sec

Part 3

Also together with atomic bool you can use condition variable inside while loop to use it for pausing purpose instead of std::this_thread::sleep_for().

Condition variable will help on the very last iteration before stop, will help to terminate early before whole pause has finished.

You can see in the example below in console output that server has stopped early at 1.1 sec compared to 1.2 sec in the example of Part 2 of my answer.

Try it online!

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

class Server {
public:
    void runForever();
    void shutDown();
private:
    std::atomic<bool> doStop_ = false;
    std::mutex mutex_;
    std::condition_variable CV;
};

void Server::runForever() {
    while (!doStop_) {
        std::cout << "Doing computation..." << std::endl;
        
        std::unique_lock<std::mutex> lck(mutex_);
        CV.wait_for(lck, std::chrono::milliseconds(200),
            [this]{ return bool(doStop_); });
    }
}

void Server::shutDown() {
    {
        std::lock_guard<std::mutex> lck(mutex_);
        doStop_ = true;
    }
    CV.notify_all();
}

int main() {
    auto const time_begin = std::chrono::high_resolution_clock::now();
    auto TimeElapsed = [&]{
        return double(std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::high_resolution_clock::now() - time_begin).count()) / 1000;
    };

    Server server;
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    std::thread t0([&]{
        std::cout << "Server started at " << TimeElapsed() << " sec" << std::endl;
        server.runForever();
        std::cout << "Server stopped at " << TimeElapsed() << " sec" << std::endl;
    });
    std::this_thread::sleep_for(std::chrono::milliseconds(900));
    server.shutDown();
    t0.join();
}

Output:

Server started at 0.203 sec
Doing computation...
Doing computation...
Doing computation...
Doing computation...
Doing computation...
Server stopped at 1.1 sec

CodePudding user response:

Thanks molbdnilo, I got an solution for my problem now.

It can be solve using wait_until.

Server::runForever ()
{
Using namespace std::literals::chrono_literals;
while(!doStop_)
{
unique_lock<mutex> lck(stop);
//Do something, it should be looping in while loop
auto now = std::chrono::system_clock::now();
CV.wait_until(lck, now 100ms [this]() {return doStop_;});
}

If any other method is there,let me know and feel free to share pseudocode while explaining.

  • Related