C++ 11 adds the standard threading library: STD ::thread, which provides threading support at the language level and is cross-platform. On different operating systems, platform-dependent thread libraries, such as Linux, have the underlying implementation of the PThread library. STD :: Thread disallows copy constructors and copy assignment operators, so STD :: Thread objects cannot be copied, but can be moved.

Basic knowledge of

The simplest example is:

void func(int arg1, int arg2) {
    cout << "arg1: " << arg1 << ", arg2: " << arg2 << endl;
    cout << "child thread id: " << std::this_thread::get_id() << endl;
}
int main(a) {
    // Create and start the child thread
    std: :thread thread(func, 10.100);
    cout << "child thread id: " << thread.get_id() << endl;
    thread.join();

    cout << "main thread id: " << std::this_thread::get_id() << endl;
    cout << "main thread exit" << endl;
    return 0;
}

/ / output
child thread id: 0x70000e841000
arg1: 10, arg2: 100
child thread id: 0x70000e841000
main thread id: 0x10ef98dc0
main thread exit
Copy the code

The STD :: Thread object supports the following operations:

  • Get_id (): gets the ID of the thread. The type is STD :: Thread :: ID
  • Join (): This function blocks the current thread until the specified thread completes execution.
  • Joinable (): Whether a thread can be joined. There are two states: Joinable and detached. The former must terminate the thread with the join function to release resources, and the latter will release resources after the thread finishes executing.
  • Detach (): Changes the thread from joinable to detached.
  • Swap (Thread & __t): Swaps the underlying handle represented by two thread objects
  • Native_handle (): Returns the underlying handle becausestd::threadImplementation is operating system dependent, so this function returnsstd::threadThe thread handle of the underlying implementation, for example, under the Posix standard, represents the Pthread handlepthread_t.
  • Static unsigned hardware_concurrency(): Returns the number of concurrent threads supported by the underlying thread implementation of the current platform.

This_thread STD ::this_thread represents the current thread. This_thread is actually a namespace and supports the following operations:

  • Get_id (): gets the ID of the thread. The type is STD :: Thread :: ID
  • sleep_for(const chrono::duration<_Rep, _Period>& __d): Duration specified by sleep, STD ::chrono creates type aliases for duration with different time dimensions. For example, STD ::chrono::seconds(5) stands for 5 seconds; STD: : chrono: : milliseconds (500) 500 milliseconds.
  • Sleep_until (const chrono::time_point<_Clock, _Duration>& __t): sleep to the absolute time
  • Sleep_until (const chrono::time_point
    <:steady_clock _duration="">
    & __t): sleep to absolute time
    ,>
  • Yield: the current thread gives up execution and the operating system schedules another thread to continue execution.

Use sleep_for and sleep_until to sleep for 10 seconds each:


// The current timestamp
std: :time_t timestamp = std::chrono::system_clock::to_time_t(system_clock::now());
// sleep_until implements sleep for 10 seconds
std::this_thread::sleep_until(system_clock::from_time_t(timestamp + 10));

// sleep_for implements sleep for 10 seconds
std::this_thread::sleep_for(std::chrono::seconds(10));
Copy the code

Refer to the time.h file for time operations

// Current timestamp (s)
std: :time_t now_timestamp = system_clock::to_time_t(system_clock::now());
// struct tm contains hours, minutes, and seconds
struct std::tm *now_tm = std::localtime(&now_timestamp);
std: :cout << "Current time: " << now_tm->tm_hour << ":" << now_tm->tm_min << ":"<< now_tm->tm_sec << '\n';
// One minute to come
++now_tm->tm_min;
// Future timestamp
std: :time_t future_timestamp = std::mktime(now_tm);
std: :cout << "Future time: " << now_tm->tm_hour << ":" << now_tm->tm_min << ":"<< now_tm->tm_sec << '\n';
// Time_point format, available with STD ::this_thread::sleep_until
std::chrono::time_point<system_clock> x = system_clock::from_time_t(future_timestamp);

/ / output
Current time: 19:3:22
Future time: 19:4:22
Copy the code

Thread lock

The mutex

The mutex (lock)

STD ::mutex stands for mutex and cannot be copied (the copy constructor and copy assignment operators are removed). Recursive locking is not supported. If required, use STD :: RECURsiVE_mutex instead.

Supported operations:

  • Void lock() : locks the mutex. If the mutex is not locked by another thread, the thread is called to lock it. If the mutex is already locked by another thread, the calling thread will block until the mutex is unlocked by another thread. If a mutex has already been locked by the calling thread, locking the same mutex again will result in a deadlock (undefined behavior) that can be usedrecursive_mutexInstead, it allows the same thread to lock the same thread multiple timesrecursive_mutex.
  • Bool try_lock() : Attempts to lock the mutex, if the mutex is not locked by another thread, the call thread locks it, and try_lock returns true; If the mutex is already locked by another thread, the calling thread cannot lock the mutex, and try_lock returns false (not blocking); If a mutex has already been locked by the calling thread, locking the same mutex again will result in a deadlock (undefined behavior) that can be usedrecursive_mutexInstead, it allows the same thread to lock the same thread multiple timesrecursive_mutex.
  • Void unlock() : Unlocks the mutex. The calling thread releases the mutex. Other threads blocked in the lock function can continue to lock the mutex.

The main difference between lock and try_lock is that mutex cannot be locked. The lock function blocks the calling thread until the mutex can be locked. Try_lock, on the other hand, does not block the calling thread, but returns directly with a value of false.

Recursive_mutex (reentrant lock)

Recursive_mutex: allow the same thread to lock the same RECURsive_MUtex multiple times. Unlock also needs to be called the same number of times so that the calling thread can release ownership of recursive_mutex and other threads can lock recursive_mutex.

Timed_mutex (time lock)

Timed_mutex adds two member functions try_lock_for and try_lock_until to mutex, which means to wait for a period of time to try to acquire the lock.

  • Bool trY_LOCK_for (const chrono::duration<_Rep, _Period>& __d) : block for a period of time, try to lock timed_mutex. If timed_mutex is not locked by another thread, the thread is called to lock it and the function returns true. If timed_mutex is already locked by another thread, the calling thread cannot lock timed_mutex. The function blocks the duration specified by the argument, during which time the function returns true if timed_mutex is successfully locked (timed_mutex is released by another thread). Otherwise, the function returns false, indicating that no lock was acquired within the specified duration. If timed_mutex has already been locked by the calling thread, locking the same timed_mutex again will result in a deadlock (undefined behavior) that can be usedrecursive_timed_mutexInstead, it allows the same thread to lock the same thread multiple timesrecursive_timed_mutex.
  • Bool trY_LOCK_UNTIL (const chrono::time_point<Clock,Duration>& ABS_time) : behavior andtry_lock_forYes, but the parameter is absolute time.
std::timed_mutex timed_mutex;
// Try to acquire the lock within 2 seconds
if (timed_mutex.try_lock_for(std::chrono::seconds(2))) {   
    // do some thing            
    / / releases the lock
    timed_mutex.unlock();
}

// The current absolute timestamp
std: :time_t timestamp = std::chrono::system_clock::to_time_t(system_clock::now());
// Try to block to some future absolute time to get the lock
if (timed_mutex.try_lock_until(system_clock::from_time_t(timestamp + 10))) {   
    // do some thing            
    / / releases the lock
    timed_mutex.unlock();
}

Copy the code

Recursive_timed_mutex (reentrant time lock)

A reentrant time lock with timed_MUtex and RECURsive_mutex capabilities.

🔐 lock hierarchy

For locks with different capabilities, C++ has three levels:

  1. BasicLockable: locks that support lock and unlock.
  2. Lockable: Supports try_lock locks based on BasicLockable, for example, MUtex and RECURsive_mutex.
  3. TimedLockable: Supports try_lock_for and TRy_lock_until locks based on Lockable, for example, timed_mutex and RECURsive_timed_mutex.

The Lock template class

lock_guard

Lock_guard is an object that manages mutex, is not copy-able (the copy constructor and copy assignment operators are removed), and has no member functions other than those synthesized by default. When the lock_guard is constructed, the mutex is locked by the calling thread, and when the LOCK_guard is destroyed, the mutex is unlocked by the calling thread. This way, you can ensure that mutex is unlocked correctly when your program throws an exception. The mutex here can be any of the four locks. The lock_guard does not interfere with the MUtex life cycle, and the program must ensure that the mutex life cycle is extended at least until the lock_guard holding it is destroyed.

In simple terms, when a lock_guard is constructed, the lock is acquired; When the lock_guard is destructed, the lock is released.

In addition to the single-argument constructor, lock_guard has a two-argument constructor: Lock_guard (mutex_type& __m, adopt_lock_t), which means that when lock_guard is created, the constructor does not lock the MUtex. Instead, an external program guarantees that the mutex is locked.

Here’s a typical example:

/ / the mutex
std::mutex mtx;

void print_even(int x) {
    if (x % 2= =0) std: :cout << x << " is even\n";
    else throw (std::logic_error("not even"));
}

void print_thread_id(int id) {
    try {
        // Lock MTX with lock_guard to ensure that even with exception logic, lock_guard can unlock MTX on destruct
        std::lock_guard<std::mutex> lck(mtx);
        print_even(id);
    }
    catch (std::logic_error &) {
        std: :cout << "[exception caught]\n"; }}int main(a) {
    std::thread threads[10];
    for (int i = 0; i < 10; ++i)
        threads[i] = std::thread(print_thread_id, i + 1);

    for (auto &th : threads) th.join();

    return 0;
}

// Possible output
[exception caught]
6 is even
4 is even
[exception caught]
[exception caught]
2 is even
[exception caught]
8 is even
[exception caught]
10 is even
Copy the code

unique_lock

Unique_lock is an object that manages mutex. It is not copy-able (the copy constructor and the copy assignment operator are removed). Member functions such as LOCK, try_lock, try_lock_for, try_lock_until, and unlock have been added (the roles of these functions are described above at 👆) to make them more flexible, but with some performance impact. Here is the class definition in the source code:

Template <class _Mutex> class unique_lock {public: // template parameters, typedef _Mutex mutex_type; Private: // lock mutex_type* __m_; // Whether the lock has been obtained bool __owns_; Public: // constructor unique_lock() : __m_(nullptr), __owns_(false) {} // Single-argument constructor, like lock_guard, makes explicit unique_lock(mutex_type& __m) : __m_(_VSTD::addressof(__m)), __owns_(true) {__m_->lock(); Unique_lock (mutex_type& __m, defer_lock_t) _NOEXCEPT: __m_ (_VSTD: : addressof (__m)), __owns_ (false) {} / / take try_to_lock_t constructor (the second parameter can be used to compile the const STD: : try_to_lock), Constructor tries to lock unique_lock(mutex_type& __m, try_to_lock_t) : __m_ (_VSTD: : addressof (__m)), __owns_ (__m. Try_lock ()) {} / / take adopt_lock_t constructor (the second parameter can be used to compile the const STD: : adopt_lock), As with lock_guard, there is no lock in the constructor, but the default is that the external program has already locked unique_lock(mutex_type& __m, adopt_lock_t) : __m_(_VSTD::addressof(__m)), __owns_(true) {} template <class _Clock, Class _Duration> // Try to lock unique_lock(mutex_type& __m, const chrono::time_point<_Clock, _Duration>& __t) : __m_(_VSTD::addressof(__m)), __owns_(__m.try_lock_until(__t)) {} template <class _Rep, Unique_lock (mutex_type& __m, const chrono::duration<_Rep, _Period>& __d) : __m_ (_VSTD: : addressof (__m)), __owns_ (__m. Try_lock_for (__d)) {} / / destructor, if the lock, The lock is released ~ unique_lock () {if (__owns_) __m_ - > unlock (); } private: // Delete the copy constructor and assignment operator unique_lock(unique_lock const&); // = delete; unique_lock& operator=(unique_lock const&); // = delete; Public: #ifndef _LIBCPP_CXX03_LANG // move constructor unique_lock(unique_lock&& __u) _NOEXCEPT: __m_(__u.__m_), __owns_(__u.__owns_) {__u.__m_ = nullptr; __u.__owns_ = false; } // Move assignment operator unique_lock& operator=(unique_lock&& __u) _NOEXCEPT {if (__owns_) __m_->unlock(); __m_ = __u.__m_; __owns_ = __u.__owns_; __u.__m_ = nullptr; __u.__owns_ = false; return *this; } #endif // libcpp_cxx03_lang void lock(); // Try to lock bool try_lock(); template <class _Rep, class _Period> bool try_lock_for(const chrono::duration<_Rep, _Period>& __d); template <class _Clock, class _Duration> bool try_lock_until(const chrono::time_point<_Clock, _Duration>& __t); Void unlock(); / / swap void swap (unique_lock & __u) _NOEXCEPT {_VSTD: : swap (__m_, __u. __m_); _VSTD::swap(__owns_, __u.__owns_); } // return the held mutex, but this function does not unlock mutex mutex_type* release() _NOEXCEPT {mutex_type* __m = __m_; __m_ = nullptr; __owns_ = false; return __m; Bool owns_lock() const _NOEXCEPT {return __owns_; } // Overloaded operator bool () const _NOEXCEPT {return __owns_; } // Get hold of mutex mutex_type* mutex() const _NOEXCEPT {return __m_; }};Copy the code

The unique_lock constructor implements different locking strategies, see the above source code and comments.

Use the more efficient lock_guard in preference unless necessary.

Global function

std::call_once

Call_once is a global function template with the following prototype:

template <class Fn.class.Args>
  void call_once (once_flag& flag.Fn&& fn.Args&&... args);
Copy the code

Call_once calls the fn function with the args argument unless another thread has (or is) calling call_once with the same once_flag.

The first thread that calls call_once using the same once_flag will execute the FN function, and the other thread that calls call_once using the same once_flag will enter the passive execution state, that is, the other thread will not execute the FN function, but will block until the first thread that executes the FN function ends.

If the thread executing the FN function via call_once throws an exception, and there are passive threads, one of them is selected to execute the FN function.

If a thread has finished executing the call_once, that is, the fn function returns, then all current passive threads and future calls to call_once (using the same once_flag) will return immediately and the FN function will not be executed again. That is, in a multi-threaded environment, the same once_flag call_once call will only execute the FN function once.

Here’s a practical example:

int winner;

void set_winner(int x) {
    winner = x;
    std: :cout << "set_winner, x = " << x << std: :endl;
}

// Multiple threads share one once_flag
std::once_flag winner_flag;
// Thread function
void wait_1000ms(int id) {
    // Loop 1000 times, sleep 1ms each time
    for (int i = 0; i < 1000; ++i)
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
        // When a thread calls call_once through the same once_flag, only one thread will execute the corresponding set_winner function
        std::call_once(winner_flag, set_winner, id);
}

int main(a) {
    std::thread threads[10];
    // 10 threads
    for (int i = 0; i < 10; ++i)
        threads[i] = std::thread(wait_1000ms, i + 1);

    std: :cout << "waiting for the first among 10 threads to count 1000 ms... \n";

    for (auto &th : threads) th.join();
    std: :cout << "winner thread: " << winner << '\n';

    return 0;
}

// Possible output
waiting for the first among 10 threads to count 1000 ms...
set_winner, x = 8
winner thread: 8
Copy the code

As you can see, although 10 threads are running, only one thread ends up executing set_winner.

std::lock

Lock is a global function template with the following prototype:

template <class Mutex1.class Mutex2.class.Mutexes>
  void lock (Mutex1& a.Mutex2& b.Mutexes&... cde);
Copy the code

The function locks all mutex, blocking the calling thread if necessary.

The lock function calls all mutex member functions in an indeterminate order: lock, try_lock, and unlock, ensuring that when the function returns, all mutex is locked (without any deadlocks).

If the lock function cannot lock all mutex(for example, if one of the calls threw an exception), then all mutex that it successfully locked are unlocked before the function fails.

Here’s an example of locking both foo and bar at 👇 :

std::mutex foo, bar;
void task_a(a) {
    // if foo.lock() is used; bar.lock(); Instead of STD :: Lock, it is possible that Task_A locks foo and Task_B locks bar, resulting in a deadlock
    // foo.lock(); bar.lock(); 
    std::lock(foo, bar);
    std: :cout << "task a\n";
    foo.unlock();
    bar.unlock();
}

void task_b(a) {
    // If bar.lock() is used; foo.lock(); Instead of STD :: Lock, it is possible that Task_A locks foo and Task_B locks bar, resulting in a deadlock
    // bar.lock(); foo.lock();
    std::lock(bar, foo);
    std: :cout << "task b\n";
    bar.unlock();
    foo.unlock();
}

int main(a) {
    std: :thread th1(task_a);
    std: :thread th2(task_b);

    th1.join();
    th2.join();

    return 0;
}
Copy the code

std::try_lock

Try_lock is a global function template with the following prototype:

// int Return value: -1 if all mutex locks successfully; Otherwise, return the index of the mutex that failed to lock
template <class Mutex1.class Mutex2.class.Mutexes>
  int try_lock (Mutex1& a.Mutex2& b.Mutexes&... cde);
Copy the code

The mutex.try_lock function attempts to lock all mutex.

The try_lock function locks the mutex arguments (first a, then B, and finally CDE) through the mutex.try_lock member function until either all calls succeed or any calls fail (that is, mutex.try_lock returns false or throws an exception).

If the try_lock function returns due to a mutex lock failure, it unlocks all previous mutex locks and returns the index of the mutex that failed to lock.

std::mutex foo, bar;

void task_a(a) {
    foo.lock();
    std: :cout << "task a\n";
    bar.lock();
    // ...
    foo.unlock();
    bar.unlock();
}

void task_b(a) {
    int x = try_lock(bar, foo);
    if (x == - 1) {
        std: :cout << "task b\n";
        // ...
        bar.unlock();
        foo.unlock();
    } else {
        std: :cout << "[task b failed: mutex " << (x ? "foo" : "bar") < <" locked]\n"; }}int main(a) {
    std: :thread th1(task_a);
    std: :thread th2(task_b);

    th1.join();
    th2.join();

    return 0;
}
Copy the code

Thread synchronization

condition_variable

Condition_variable is a synchronization primitive that blocks the calling thread until the other thread tells it to resume. Condition_variable is non-copy-able (the copy constructor and the copy assignment operator are removed). Unique_lock (via mutex) is used to lock the thread when condition_variable arbitrary wait is called. This thread will block until another thread wakes up with any notify of the same condition_variable.

Condition_variable object always implements thread blocking using STD ::unique_lock

.

Condition_variable condition_variable

wait

There are two overloaded versions of wait, as follows:

void wait (unique_lock<mutex>& lck);
	
template <class Predicate>
  void wait (unique_lock<mutex>& lck.Predicate pred);
Copy the code

Blocks the calling thread (which must have locked mutex held by unique_lock before calling wait) until it is woken up by another thread. When this function blocks the calling thread, unique_lock.unlock is automatically called to unlock the mutex, allowing other threads to lock the same mutex. Once awakened by another thread, the function unblocks and calls unique_lock.lock to re-lock (possibly blocking the calling thread again), restoring unique_lock to the state in which it was when the wait function was called.

Normally, another thread calls the notify_ONE or notify_all member function of condition_variable to wake up the blocked thread. However, some implementations may generate a false wake up without calling any notify functions. Therefore, programs using this function should ensure that their recovery conditions are met, so the wait function is called in a loop structure, as shown below, so that even if it is falsely awakened, it will block again because the conditions are not met.

while{condition_variable.wait}Copy the code

In overloaded versions that include the _Predicate __pred argument, _Predicate is the argument to the function template, representing the function that returns a Boolean value. If __pred returns false, the function will always block, and notify will wake up the thread only if it returns true. __pred is called until it returns true, perfect for handling false wake up problems, as shown below:

template <class _Predicate>
void
condition_variable::wait(unique_lock<mutex>& __lk, _Predicate __pred)
{
    // if __pred() returns false, wait blocking is always called
    while(! __pred()) wait(__lk); }Copy the code

Overall, there are three caveats to wait functions:

  1. To call a wait function, the calling thread must have locked mutex,
  2. When a wait class function is called, it does two things:
  • Block the calling thread.
  • Call unique_lock.unlock to unlock the MUtex that the calling thread has locked.
  1. When a wait function is awakened, it also does two things:
  • Unblock the calling thread.
  • Call unique_lock.lock to re-lock the calling thread.

This ensures that the wait class function is in the same thread state before being called and after being awakened.

Below 👇 look at a case case:

std::mutex mtx;
std::condition_variable cv;

int cargo = 0;

bool shipment_available(a) { returncargo ! =0; }

void consume(int n) {
    for (int i = 0; i < n; ++i) {
        std::unique_lock<std::mutex> lck(mtx);
        cv.wait(lck, shipment_available);
        // consume:
        std: :cout << cargo << '\n';
        cargo = 0; }}int main(a) {
    std: :thread consumer_thread(consume, 10);

    // produce 10 items when needed:
    for (int i = 0; i < 10; ++i) {
        while (shipment_available()) std::this_thread::yield();
        std::unique_lock<std::mutex> lck(mtx);
        cargo = i + 1;
        cv.notify_one();
    }

    consumer_thread.join();

    return 0;
}

/ / output
1
2
3
4
5
6
7
8
9
10
Copy the code

The child thread blocks because cargo is zero, and then the main thread modifies cargo and wakes up the child thread.

wait_for

There are two overloaded versions of wait_for, as follows:

enum class cv_status{
    no_timeout,
    timeout
}
// Stop blocking if the time is up, return timeout, otherwise return no_timeout
cv_status wait_for(unique_lock<mutex>& __lk, const chrono::duration<_Rep, _Period>& __d);
// Returns pred() regardless of whether a timeout is triggered (although it can only be false if a timeout is triggered).
bool wait_for (unique_lock<mutex>& lck, const chrono::duration<Rep,Period>& rel_time, Predicate pred);
Copy the code

The wait_for function adds the ability to block duration to wait, so there are two ways to wake up:

  • Is actively woken up by another thread through notify.
  • When a duration is blocked, it stops blocking as if it were awakened by another thread.

👇 Here is a practical example:

std::condition_variable cv;
int value;
void read_value(a) {
    std: :cin >> value;
    cv.notify_one();
}

int main(a) {
    std: :cout << "Please, enter an integer (I'll be printing dots): \n";
    std: :thread th(read_value);

    std::mutex mtx;
    std::unique_lock<std::mutex> lck(mtx);
    while (cv.wait_for(lck, std::chrono::seconds(1)) = =std::cv_status::timeout){
        std: :cout << '. ' << std: :endl;
    }
    std: :cout << "You entered: " << value << '\n';

    th.join();

    return 0;
}
Copy the code

The main thread blocks for one second at a time, and if wait_for ends with a timeout, it prints., and blocks again until the quilt thread wakes up and exits the while loop, ending the main thread.

wait_until

There are two overloaded versions of wait_until, as follows:

enum class cv_status{
    no_timeout,
    timeout
}
// Stop blocking if the time is up, return timeout, otherwise return no_timeout
cv_status wait_until (unique_lock<mutex>& lck, const chrono::time_point<Clock,Duration>& abs_time);
// Returns pred() regardless of whether a timeout is triggered (although it can only be false if a timeout is triggered).
bool wait_until (unique_lock<mutex>& lck, const chrono::time_point<Clock,Duration>& abs_time, Predicate pred);
Copy the code

The wait_until function adds the ability to block to absolute time on top of wait, so there are two ways to wake up:

  • Is actively woken up by another thread through notify.
  • When the absolute time for blocking is up, the block is stopped, just as it was woken up by another thread.

notify_all

Unblock all threads currently waiting for condition_variable to be specified. If there are no threads waiting, the function does nothing.

Wake up all waiting threads

notify_one

Unblock any of the threads currently waiting on condition_variable. If there are no threads waiting, the function does nothing.

Wake up a waiting thread at random

Must notify_all and notify_one be invoked when locked

Is not mandatory, can according to specific circumstances, decide whether to add a lock

Notifying threads to call notify_all or notify_ONE does not require an advance lock (the same MUTEX that the waiting thread locks). In practice, locking and notifying later is a pessimistic approach because the notified waiting thread immediately blocks again, waiting for the notifying thread to release the lock. However, some implementations (especially PThreads) recognize this and avoid this “rush wait” scenario by transferring the wait thread from condition_variable queue directly to mutex queue in notify without waking it up.

But if you need accurate event scheduling, so after the first lock notification is necessary, for example: the waiting thread to exit the program directly after meet the conditions, this will lead to inform thread condition_variable is destroyed, in order not to let the waiting thread gets the lock immediately, then notice under the lock state may be necessary.

condition_variable_any

Unique_lock condition_wait /wait_for/wait_until condition_variable But condition_variable_any’s wait/wait_for/wait_until function can take any Lockable lock as an argument. Other than that, the abilities are exactly the same. Condition_variable. wait

std::mutex mtx;
std::condition_variable_any cv;

int cargo = 0;

bool shipment_available(a) { returncargo ! =0; }

void consume(int n) {
    for (int i = 0; i < n; ++i) {
        mtx.lock();
        cv.wait(mtx, shipment_available);
        // consume:
        std: :cout << cargo << '\n';
        cargo = 0; mtx.unlock(); }}int main(a) {
    std: :thread consumer_thread(consume, 10);

    // produce 10 items when needed:
    for (int i = 0; i < 10; ++i) {
        while (shipment_available()) std::this_thread::yield();
        mtx.lock();
        cargo = i + 1;
        cv.notify_one();
        mtx.unlock();
    }

    consumer_thread.join();

    return 0;
}
Copy the code

Refer to the article

  1. c++ multithreading