Key points in this chapter
* Wait for Event
* Use futures to wait for one-time events (waiting for one-off events with futures)
* Wait time limit
* Use synchronous operation to simplify code
This chapter mainly describes how to use conditional variables and futures to wait for events, and how to use them to make thread synchronization easier.
CP4
1. Waiting for events or other conditions
A. If one thread waits for the result of another thread's processing, it can take a non-stop detection of the shared resource, and then modify the shared resource when another thread finishes a specific operation. This thread detects that subsequent actions are performed after the modification, but this approach requires constant detection of shared resource inefficiencies that can waste a lot of system resources.
B. Use Std::this_thread::sleep_for () to let the waiting thread sleep for a while
BOOL Flag;std::mutex m;void Wait_for_flag () { std::unique_lock<std::mutex> lk (m); while (!flag) { lk.unlock (); Std::this_thread::sleep_for (Std::chrono::milliseconds ()); Lk.lock (); }}
But this approach has drawbacks, too short of sleep time can waste system resources, too long sleep time will cause the target thread to finish work after waiting for the thread still sleep.
C. Waking a thread in sleep after a specific condition is completed using a condition variable (condition variable)
1) std::condition_variable Std::condition_variable_any. Requires # include <condition_variable>
The former needs to be used in conjunction with Std::mutex, which can match any class of mutex objects.
#include <queue> #include <iostream> #include <mutex> #include <thread> #include <condition_ variable> #include <sstream>std::mutex mut;std::queue<int> data_queue;std::condition_variable data_ Cond;//bool more_data_to_prepared (); void Data_preparation_thread () {for (int i = 0; i<=; i++) {int data = i; Std::lock_guard<std::mutex> LK (mut); Data_queue.push (data); Data_cond.notify_one (); }}void Data_processing_thread () {while (true) {std::unique_lock<std::mutex> lk (mut); Data_cond.wait (LK, []{return!data_queue.empty ();}); int data = Data_queue.front (); Data_queue.pop (); Lk.unlock (); std::cout<< "Data poped! "<<data<<std::endl; if (data = =) break; }}int Main () {Std::thread T1 (data_preparation_thread); Std::thread T2 (Data_processing_thread); T1.join (); T2.join (); return 0;}
Using the queue (std::d eque) to transfer data between threads is also a common method, which can reduce many synchronization problems and resource competition problems.
2) thread-safe queue using condition variables
#include <memory> #include <queue> #include <iostream> #include <mutex> #include <thread> #include <condition_variable>template<typename t>class threadsafe_queue{private: mutable Std::mutex Mut; The mutex must be mutable because it'll be modified by the const function. std::queue<t> Data _queue; std::condition_variable data_cond;public: Threadsafe_queue () { & nbsp } Threadsafe_queue (const threadsafe_queue& Other) { Std::lock_ Guard<std::mutex> lk (other.mut); Data_queue = other.data_queue; } & nbsp threadsafe_queue& operator= (const threadsafe_queue&) = delete; void push (T new_value) { std::lock_guard<std::mutex> LK (mut); Data_queue.push (new_ Value); Data_cond.notify_one (); } BOOL Try_pop (t& value) { std::lock_guard<std::mutex> LK (mut); if (Data_queue.empty ()) return false; value = Data_queue.pop (); Return true; } std::shared_ptr<t> Try_pop () { std: :lock_guard<std::mutex> lk (mut); if (Data_queue.empty ()) & nbsp return false; std::shared_ptr<t> Res (std::make_shared<t> (data_ Queue.front ()); Data_queue.pop (); return res; } std::shared_ptr<t> Wait_and_pop () { STD::UNIQUE_LOCK<STD:: Mutex> lk (mut); Data_conD.wait (LK, [This]{return!data_queue.empty ();}); std::shared_ptr<t> Res (std::make_shared<t> (Data_queue.front ())); Data_queue.pop (); return res; } bool Empty () con st { std::lock_guard<std::mutex> LK (mut); return Data_queue.empty (); };
An example of using a condition variable to control three threads looping output a,b,c in a fixed order
#include <memory> #include <iostream> #include <mutex> #include <thread> #include <condition _variable>class Threads_rolling{private:std::mutex Mut; Std::condition_variable M_cond; Char M_flag; BOOL M_firstrun;public:threads_rolling (): M_flag (' A '), M_firstrun (true) {} void Thread_a () {while (true) {std::unique_lock<std::mutex> lk (mut); if (m_firstrun) {m_firstrun = false; }else {m_cond.wait (LK, [=]{return m_flag = = ' A ';}); } m_flag = ' B '; std::cout<< "Output from Thread a!" <<std::endl; M_cond.notify_all (); }} void Thread_b () {while (true) {std::unique_lock<std::mutex> lk (mut); M_cond.wait (LK, [=]{return m_flag = = ' B ';}); M_flag = ' C '; std::cout<< "Output from Thread b!" <<std::endl; M_cond.notify_all (); }} void Thread_c () {while (true) {std::unique_lock<std::mutex> lk (mut); M_cond.wait (LK, [=]{return m_flag = = ' C ';}); M_flag = ' A '; Lk.unlock (); std::cout<< "Output from Thread c!" <<std::endl; M_cond.notify_all (); }}};int Main () {threads_rolling threads; Std::thread T1 (&threads_rolling::thread_a, &threads); Std::thread T2 (&threads_rolling::thread_b, &threads); std::thread T3 (&threads_rolling::thread_c, &threads); T1.join (); T2.join (); T3.join (); Use the CTRL + C to exit the test program! return 0;}
2. Use futures to wait for one-time events
Std::furute<> only one future associated with an event
Std::shared_future<> multiple future can be associated with the same event
1) return value from background task
A. Returning a value from an asynchronous task Std::async () syntax with Std::thread ()
#include <memory> #include <queue> #include <iostream> #include <mutex> #include <thread> #include <future> #include <condition_variable>int find_the_answer_to_ltuae (); void Do_other_stuff (); int Main () { std::future<int> the_answer = Std::async (find_the_answer_to_ltuae); Do_other_stuff (); std::cout<< "The answer is" <<the_answer.get () <<std::endl; return 0;}
Basic usage of Std:async ()
struct x{ void foo (int, std::string const&); std::string Bar (std::string const&); X X;auto f1 = Std::async (&x::foo, &x, A, "Hello"), Auto F2 = Std::async (&x::bar, X, "Goodbye"); struct y{ D Ouble operator () (double);}; Y Y;auto F3 = Std::async (Y (), 3.141), Auto F4 = Std::async (Std::ref (y), 2.718); X Baz (x&); Std::async (Baz, Std::ref (X)); Class Move_only{public: move_only (); Move_only (move_only&&); Move_only (move_only const&) = delete; move_only& operator = (move_only&&); move_only& operator = (move_only const&) = delete; void operator () ();}; Auto F5 = Std::async (Move_only ());
Auto F6 = Std::async (Std::launch::async, Y (), 1.2); Run in new Thread.auto F7 = Std::async (std::launch::d eferred, Baz, Std::ref (x)); Run in wait () or get () Auto F8 = Std::async ( std::launch::d eferred | std::launch::async, baz, Std::ref (x)); Implementation Choosesauto F9 = Std::async (Baz, Std::ref (x)); f7.wait (); Invoke deferred function.
2) Use future to unite tasks
std::p Ackaged_task Wraps a callable object and allows the result of the callable object to be obtained asynchronously, from the wrapper callable object in the sense that std::p ackaged_task is similar to std::function, except std:: Packaged_task passes the execution result of the callable object that it wraps to a Std::future object that typically gets STD in another thread::p The result of the execution of the Ackaged_task task).
std::p Ackaged_task object contains two most basic elements, one, the wrapped task (stored task), a task is a callable object, such as a function pointer, a member function pointer, or a function object, two shared state , which is used to save the return value of the task, you can achieve the effect of asynchronously accessing the shared state through the Std::future object.
You can obtain the Std::future object associated with the shared state by using std::p ackged_task::get_future. After calling the function, two objects share the same shared state, as explained below:
std::p The Ackaged_task object is an asynchronous Provider that sets the shared state value at some point by invoking the wrapped task.
The Std::future object is an asynchronous return object that allows you to get a shared state value and, of course, waits for the shared state flag to become ready when necessary.
#include <iostream> #include <mutex> #include <thread> #include <future> #include <chrono >int countdown (int start, int end) {for (int i = start; i!= end; i.) { STD::COUT<<I<<STD:: Endl; Std::this_thread::sleep_for (Std::chrono::seconds (1)); } std::cout<< "finished!\n"; return start-end;} int main () { std::p ackaged_task<int (int, int) > Task (countdown); std::future<int> ret = Task.get_future (); Std::thread th (Std::move (Task), 0); int value = Ret.get (); std::cout<< "The countdown lasted for" <<value << "seconds.\n"; Th.join (); return 0;}
std::p ackaged_task<> template parameter is a function signature similar to int (std::string&, double*) that identifies the parameter type and return type. Example
Template<>class packaged_task<std::string (std::vector<char>*, int) >{public: template< TypeName callable> Explicit Packaged_task (callable&& f); Std::future<std::string> get_future (); void operator () (std::vector<char>*, int);};
std::p ackaged_task becomes a function object, std::function, which can be passed as a parameter to Std::thread
An example of transferring tasks between threads
#include <deque> #include <iostream> #include <mutex> #include <thread> #include <future> #include <utility>std::mutex m;std::d eque<std::p ackaged_task<void () >> Tasks;bool Gui_shutdown_ Message_received (), void Get_and_process_gui_message (), void Gui_thread () {while (!gui_shutdown_message_received ()) { Get_and_process_gui_message (); std::p ackaged_task<void () > task; {std::lock_guard<std::mutex> lk (m); if (Tasks.empty ()) continue; Task = Std::move (Tasks.front ()); Tasks.pop_front (); } task (); }}std::thread Gui_bg_thread (gui_thread); Template<typename func>std::future<void> Post_task_for_gui_ Thread (Func f) {std::p ackaged_task<void () > Task (f); std::future<void> res = task.get_future (); Std::lock_guard<std::mutex> LK (m); Tasks.push_back (Std::move (Task)); return res;}
3) Use std::p romise
The Promise object can hold the value of a type T, which can be read by the future object (possibly in another thread), so promise also provides a means of thread synchronization. Promise object constructs can be associated with a shared state (typically Std::future) and a value of type T can be saved on the associated shared state (std::future).
You can get the future object associated with the Promise object by Get_future, and after calling the function, two objects share the same shared state
The Promise object is an asynchronous Provider that can set the value of a shared state at a certain point in time.
The future object can return the value of the shared state asynchronously or, if necessary, block the caller and wait for the shared status flag to become ready before the shared status can be obtained.
#include <iostream> #include <mutex> #include <thread> #include <future> #include <utility >void print_string (std::future<std::string>& fut) { std::string str = fut.get (); std::cout<< "The string is:" <<STR<<STD::ENDL;} int main () { std::p romise<std::string> prom; Std::future<std::string> fut = prom.get_future (); Associate with the future object. Std::thread T (print_string, Std::ref (fut)); Prom.set_value ("Hello world!"); T.join (); return 0;}
4) Save exceptions in the future
Some_promise.set_exception (Std::current_exception ());
An example of using std::p romised Single thread to manage multiple connections
#include <iostream> #include <mutex> #include <thread> #include <future> #include <utility >void process_connections (Connection_set & connections) {while (!done (connections)) {for ( Connection_iterator connection = Connections.begin (), end = Connections.end (); connection! = end; ++connection) { if (Connection->has_incoming_data ()) { Data_packet data = connection-> Incoming (); std::p romise<payload_type>& p = connection->get_promise (data.id); P.set_value (data.payload); } if (Connection->has_outgoing_data ()) { Outgoing_packet data = Connection->top_of_outgoing_queue (); Connection->send (data.payload); Data.promise.set_value (True);}}}
5) Wait for multiple threads
Multiple threads accessing a std::future at the same time can cause problems with resource contention because the Get () function of the future can only be called once, and no return object will be returned.
If multiple threads need to wait for the same event, use Std::shared_future
std::p romise<int> p;std::future<int> F (p.get_future ());std::shared_future<int> SF (Std::move (f)) ;
4. Wait for a time limit
Sometimes the customer does not want to wait, need to set a time limit.
Two members of the condition variable
Wait_for ()
Wait_until ()
1) Clocks
Clock is a class and has the following special functions
* Current Time Std::chrono::system_clock::now ()
* A value to represent the time Some_clock::time_point
* Clock trigger Std::ratio<1, 25> indicates 1 second trigger 25 times
* Steady Clock (steady-time) trigger frequency is stable and immutable
Std::chrono::high_resolution_clock
2) durations
Std::chrono::d uration<60,1>//1minute
Std::chrono::d uration<1, 1000>//1ms
Std::chrono::milliseconds MS (54802);
Std::chrono::seconds s = std::chrono::d uration_cast<std::chrono::seconds> (ms); Convert from MS to seconds.
Std::chrono::milliseconds (1234). COUNT () = 1234
std::future<int> f = std::async (Some_task);
if (f.wait_for (std::chrono::milliseconds) = = Std::future_status::ready)
Do_something_with (F.get ());
3) Time Point
Std::chrono::time_point<> The first parameter is clock, the second parameter is duration
Std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes> Reference system time, measured according to minutes
Example of using time limit to wait for a condition variable
#include <iostream> #include <mutex> #include <thread> #include <future> #include <chrono > #include <condition_variable>std::condition_variable cv;bool done;std::mutex m;bool wait_loop () { auto Const TIMEOUT = std::chrono::steady_clock::now () + std::chrono::milliseconds (+); Std::unique_lock<std::mutex> LK (m); while (!done) { if (cv.wait_until (lk, timeout) = = std::cv_status::timeout) break ; } return done;}
4) functions that support timeout
4. Use synchronous operation to simplify code
1) Programming with futures
The return value of a function is only related to the parameter's type and parameter value, regardless of any other state. No modification of shared resources occurs, no parameter resource race condition
An example of a quick sort
Template<typename t>std::list<t> sequential_quick_sort (std::list<t> input) { if (Input.empty ( ) { return input; } std::list<t> result; Result.splice (Result.begin (), Input, Input.begin ()); T const* pivot = *result.begin (); Auto Divide_point = std::p artition (Input.begin (), Input.end (), [&] (T const& t) {return t<pivot;}); Std::list<t> Lower_part; Lower_part.splice (Lower_part.end (), Input, Input.begin (), divide_point); Auto New_lower (Sequential_quick_sort (Std::move (Lower_part)); Auto New_higher (Sequential_quick_sort (Std::move (input)); Result.splice (Result.end (), new_higher); Result.splice (Result.begin (), new_lower); return result;}
Use the future to change to parallel sorting
Template<typename t>std::list<t> parallel_quick_sort (std::list<t> input) { if (Input.empty ()) { return input; } std::list<t> result; Result.splice (Result.begin (), Input, Input.begin ()); T const* pivot = *result.begin (); Auto Divide_point = std::p artition (Input.begin (), Input.end (), [&] (T const& t) {return t<pivot;}); Std::list<t> Lower_part; Lower_part.splice (Lower_part.end (), Input, Input.begin (), divide_point); Std::future<std::list<t>> New_lower (Std::async (¶llel_quick_sort<t>, Std::move (Lower_part))); Auto New_lower (Parallel_quick_sort (Std::move (Lower_part)); Auto New_higher (Parallel_quick_sort (Std::move (input)); Result.splice (Result.end (), new_higher); Result.splice (Result.begin (), New_lower.get ()); return result;}
Template<typename F, TypeName a>std::future<std::result_of<f (a&&)::type> Spawn_task (F& & F, a&& A) { typedef std::result_of<f (a&&) >::type result_type; std::p ackaged_task<result_type (a&&) > Task (Std::move (f)); std::future<result_type> Res (task.get_future ()); Std::thread T (std::move (Task), Std::move (a)); T.detach (); return res;}
2) Use synchronization to deliver messages
"C + + Concurrency in action" Reading notes three simultaneous concurrent operations