untilAndForEng

Sleep and Wait

The new time library is an important component of the threading interface. As well, as threads, locks, and condition variables understanding time. All four have in common that they can sleep, wait or block until a time point or for a duration.

Convention

The methods for handling time in multithreading programs follow a simple convention. Methods ending with _for having to be parametrized by a time duration; member functions ending with _until by a time point. I presented the time duration and time point in separate posts. Here is a concise overview of sleeping, blocking, and waiting methods.

untilAndForEng

in2min stand for a time 2 minutes in the future. 2s is a time duration of 2 seconds. Although I use auto it’s very verbose to define the time point in2min: auto in2min= std::chrono::steady_clock::now() + std::chrono::minutes(2). To the rescue, we have time literals in C++14, like 2s. C++14 has more literal for typical time durations.

Now the practice.

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

Be part of my mentoring programs:

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (starts March 2024)
  • Do you want to stay informed: Subscribe.

     

    Various waiting strategies

    First, I want to describe the various waiting strategies.

    The key idea of the program is that the promise provides its result for four shared futures. That’s possible because I used std::shared_future. Each future has a different waiting strategy. All promises and futures will be executed in different threads. For the sake of simplicity, I will, in the rest of the posts, only speak about a waiting thread, although the future is indeed waiting. You can read the details about the promise and the future here.

     

    • consumeThread1: Waits up to 4 seconds for the result of the promise.
    • consumeThread2: Waits up to 20 seconds for the result of the promise.
    • consumeThread3: Asks the promise for the result and goes back to sleep for 700 milliseconds.
    • consumeThread4: Asks the promise for the result and goes back to sleep. Its sleep duration starts with one millisecond and doubles each time.

    Here is the program.

     

      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    // sleepAndWait.cpp
    
    #include <utility>
    #include <iostream>
    #include <future>
    #include <thread>
    #include <utility>
    
    std::mutex coutMutex;
    
    long double getDifference(const std::chrono::steady_clock::time_point& tp1,const std::chrono::steady_clock::time_point& tp2){
        auto diff= tp2- tp1;
        auto res= std::chrono::duration <double, std::milli> (diff).count();
        return res;
    }
    
    voidproducer(std::promise<int>&& prom){
        std::cout << "PRODUCING THE VALUE 2011\n\n"; 
        std::this_thread::sleep_for(std::chrono::seconds(5));
        prom.set_value(2011);
    }
    
    void consumer(std::shared_future<int> fut,std::chrono::steady_clock::duration dur){
        auto start = std::chrono::steady_clock::now();
        std::future_status status= fut.wait_until(std::chrono::steady_clock::now() + dur);
        if ( status == std::future_status::ready ){
            std::lock_guard<std::mutex> lockCout(coutMutex);
            std::cout << std::this_thread::get_id() << " ready => Result: " << fut.get() << std::endl;
        }
        else{
            std::lock_guard<std::mutex> lockCout(coutMutex);
            std::cout << std::this_thread::get_id() << " stopped waiting." << std::endl;
        }
        auto end= std::chrono::steady_clock::now();
        std::lock_guard<std::mutex> lockCout(coutMutex);
        std::cout << std::this_thread::get_id() << " waiting time: " << getDifference(start,end) << " ms" << std::endl;
    }
    
    void consumePeriodically(std::shared_future<int> fut){
        auto start = std::chrono::steady_clock::now();
        std::future_status status;
        do {
            std::this_thread::sleep_for(std::chrono::milliseconds(700));
            status = fut.wait_for(std::chrono::seconds(0));
            if (status == std::future_status::timeout) {
                std::lock_guard<std::mutex> lockCout(coutMutex);
                std::cout << "     " << std::this_thread::get_id() << " still waiting." << std::endl;
            }
            else if (status == std::future_status::ready) {
                std::lock_guard<std::mutex> lockCout(coutMutex);
                std::cout << "     " << std::this_thread::get_id() << " waiting done => Result: " << fut.get() << std::endl;
            }
        } while (status != std::future_status::ready); 
        auto end= std::chrono::steady_clock::now();
        std::lock_guard<std::mutex> lockCout(coutMutex);
        std::cout << "     " << std::this_thread::get_id() << " waiting time: " << getDifference(start,end) << " ms" << std::endl;
    }
    
    void consumeWithBackoff(std::shared_future<int> fut){
        auto start = std::chrono::steady_clock::now();
        std::future_status status;
        auto dur= std::chrono::milliseconds(1);
        do {
            std::this_thread::sleep_for(dur);
            status = fut.wait_for(std::chrono::seconds(0));
            dur *= 2;
            if (status == std::future_status::timeout) {
                std::lock_guard<std::mutex> lockCout(coutMutex);
                std::cout << "         " << std::this_thread::get_id() << " still waiting." << std::endl;
            }
            else if (status == std::future_status::ready) {
                std::lock_guard<std::mutex> lockCout(coutMutex);
                std::cout << "         " << std::this_thread::get_id() << " waiting done => Result: " << fut.get() << std::endl;
            }
        } while (status != std::future_status::ready);
        auto end= std::chrono::steady_clock::now();
        std::lock_guard<std::mutex> lockCout(coutMutex);
        std::cout << "         " << std::this_thread::get_id() << " waiting time: " << getDifference(start,end) << " ms" << std::endl;
    }
    
    int main(){
        
        std::cout << std::endl;
    
        std::promise<int> prom;
        std::shared_future<int> future= prom.get_future();
        std::thread producerThread(producer,std::move(prom));
        
        std::thread consumerThread1(consumer,future,std::chrono::seconds(4));
        std::thread consumerThread2(consumer,future,std::chrono::seconds(20));
        std::thread consumerThread3(consumePeriodically,future);
        std::thread consumerThread4(consumeWithBackoff,future);
        
        consumerThread1.join();
        consumerThread2.join();
        consumerThread3.join();
        consumerThread4.join();
        producerThread.join();
        
        std::cout << std::endl;
    
    }
    

     

    I create in the main function (line 85) the promise, use the promise to create the associated future (line 86), and move the promise to a separate thread (line 87). I have to move the promise in the thread because it doesn’t support copy semantic. That will not hold for the shared futures (lines 89 – 92). They support copy semantics and can therefore be copied.

    Before I talk about the work package of the thread, let me say a few words about the auxiliary function getDifference (lines 11 – 15). The function takes two-time points and returns the time duration in milliseconds. I will use the function a few times. 

    What about the threads?

    • producerThread: Executes the function producer (lines 17 – 21) and publishes its result 2011 after 5 seconds of sleep. This is the result the futures are waiting for.
    • consumerThread1: Executes the function consumer (lines 23 – 37). The thread is waiting for at most 4 seconds (line 25) before continuing its work. This waiting period is not long enough to get the result of the promise.
    • consumerThread2: Executes the function consumer (lines 23 – 37). The thread is waiting at most 20 seconds before continuing its work.
    • consumerThread3:Executes the function consumePeriodically (lines 39 – 57). It sleeps for 700 milliseconds (line 43) and asks for the result of the promise (line 44). Because of the 0 seconds in line 44 (std::chrono::seconds(0)) there is no waiting. If the calculation result is available, it will be displayed on line 73.
    • consumerThread4:Executes the function consumeWithBackoff (lines 59 – 79). It sleeps in the first iteration 1 second and doubles by each further iteration its sleeping period. Otherwise, its strategy is similar to consumerThread3.

    Now to the synchronization of the program. The clock for determining the current time as std::cout are shared variables. But I need no synchronization. First, the method std::chrono::steady_clock::now() is thread-safe (for example, in lines 24 and 34); second, the C++ runtime guarantees that the characters will be written thread-safe to std::cout. For visual reasons, I used a std::lock_guard to wrap std::cout (for example, in lines 27, 31, and 35).

    Although the threads write one after the other to std::cout the output is not easy to understand.

    sleepAndWait

    The first output is from the promise. The remaining outputs from the futures. At first, consumerThread4 asks for the result. The output is indented by 8 characters. consumerThread4 also displays its ID. consumerThread3 is immediately following. Its output is indented by 4 characters. The output of consumerThread1 and consumerThread2 is not indented.

    • consumeThread1: Waits unsuccessfully 4000.18 ms seconds without getting the result.
    • consumeThread2: Gets the result after 5000.3 ms, although its waiting duration is up to 20 seconds.
    • consumeThread3: Gets the result after 5601.76 ms. That’s about 5600 milliseconds= 8*7000 milliseconds.
    • consumeThread4: Gets the result after 8193.81 ms. To say it differently. It waits 3 seconds too long. 

    What’s next?

    This post finishes my miniseries about the time library. I will write about embedded programming with modern C++ in the next post. Here is the overview of my plan.

     

     

     

     

     

    Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, moon, Philipp Lenk, Hobsbawm, and Charles-Jianye Chen.

    Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.

    My special thanks to Embarcadero
    My special thanks to PVS-Studio
    My special thanks to Tipi.build 
    My special thanks to Take Up Code
    My special thanks to SHAVEDYAKS

    Seminars

    I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

    Standard Seminars (English/German)

    Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

    • C++ – The Core Language
    • C++ – The Standard Library
    • C++ – Compact
    • C++11 and C++14
    • Concurrency with Modern C++
    • Design Pattern and Architectural Pattern with C++
    • Embedded Programming with Modern C++
    • Generic Programming (Templates) with C++
    • Clean Code with Modern C++
    • C++20

    Online Seminars (German)

    Contact Me

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *