std::ref is the start, but this is not enough. C ++ is guaranteed to know the changes in a variable by another thread if that variable is protected either by or
a) atomic, or
b) fence (mutex, condition_variable, etc.)
It is also advisable to synchronize threads before allowing main to complete. Note that I have a fi.get() call that blocks the main thread until the future is satisfied with the asynchronous thread.
updated code:
#include <cmath> #include <iostream> #include <thread> #include <future> #include <chrono> #include <functional> #include <atomic> // provide a means of emitting to stdout without a race condition std::mutex emit_mutex; template<class...Ts> void emit(Ts&&...ts) { auto lock = std::unique_lock<std::mutex>(emit_mutex); using expand = int[]; void(expand{ 0, ((std::cout << ts), 0)... }); } // cross-thread communications are UB unless either: // a. they are through an atomic // b. there is a memory fence operation in both threads // (eg condition_variable) int func(std::atomic<bool>& on) { while(on) { auto t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); emit(ctime(&t), "\n"); std::this_thread::sleep_for(std::chrono::seconds(1)); } return 6; } int main() { std::atomic<bool> on { true }; std::future<int> fi = std::async(std::launch::async, func, std::ref(on)); std::this_thread::sleep_for(std::chrono::seconds(5)); on = false; emit("function returned ", fi.get(), "\n"); return 0; }
Output Example:
Wed Jun 22 09:50:58 2016 Wed Jun 22 09:50:59 2016 Wed Jun 22 09:51:00 2016 Wed Jun 22 09:51:01 2016 Wed Jun 22 09:51:02 2016 function returned 6
on request, explanation emit<>(...)
template<class...Ts> void emit(Ts&&...ts)
emit is a function that returns void and accepts any number of parameters using a reference to the x-value (for example, the constant ref, ref or r-value of ref). That is, he will accept anything. This means that we can call:
emit(foo()) - call with the return value of the function (r-value)
emit(x, y, foo(), bar(), "text") - a call with two links, 2 r-values-links and a string literal
using expand = int[]; defines a type as an array of integers of indefinite length. We will use this simply to force the evaluation of expressions when we create an object of type expand . The actual array itself will be discarded by the optimizer - we just need the side effects of building it.
void(expand{ ... }); - forces the compiler to create an instance of the array, but the void cast says that we will never use the array itself.
((std::cout << ts), 0)... - for each parameter (denoted by ts), one member is expanded in the array construction. Remember that an array is an integer. cout << ts will return ostream& , so we use the comma operator to sequentially order the call to ostream<< before just evaluating the expression 0. Zero can actually be any integer. That would not matter. This is an integer that is conceptually stored in an array (which will still be discarded).
0, - The first element of the array is zero. This is the case when someone calls emit() with no arguments. Ts parameter package will be empty. If we did not have this leading zero, the final evaluation of the array would be int [] { } , which is a zero-length array that is illegal in C ++.
Further notes for beginners:
everything inside the array initialization list is an expression.
An expression is a sequence of "arithmetic" operations that leads to some object. This object can be an actual object (an instance of a class), a pointer, a reference, or a fundamental type, such as an integer.
So, in this context, std::cout << x is an expression evaluated by calling std::ostream::operator<<(std::cout, x) (or its equivalent function, depending on what x is) . The return value from this expression is always std::ostream& .
Putting an expression in parentheses does not change its value. It just orders. for example, a << b + c means "shifted left by (b plus c)", while (a << b) + c means "shifted left by b, then add c".
The comma ',' is also an operator. a(), b() means "call function a" and then discards the result and then calls function b. The return value must be the value returned by b '.
So, with some mental gymnastics, you should be able to see that ((std::cout << x), 0) means "calling std::ostream::operator<<(std::cout, x) , throw away the resulting link to stream, and then evaluate the value to 0.) The result of this expression is 0, but the side effect of streaming x to cout will happen before we get 0 '.
So, when Ts is (say) int and a pointer to a string, Ts... will be a similar list of types <int, const char*> and Ts... will be effectively <int(x), const char*("Hello world")>
Thus, the expression will expand to:
void(int[] { 0, ((std::cout << x), 0), ((std::cout << "Hello world"), 0), });
What steps in a child means:
- select an array of length 3
- array [0] = 0
- call std :: cout <x, discard the result, array [1] = 0
- call std :: cout <"Hello world", drop the result, array [2] = 0
And, of course, the optimizer sees that this array is never used (because we did not give it a name), so it removes all unnecessary bits (because optimizers do it), and it becomes equivalent:
- call std :: cout <x, discard the result.
- call std :: cout <"Hello world", throw away the result.