6

I am trying to write a thread that would run in the background of my main program and monitor sth. At some point the main program should signal the thread to safely exit. Here is a minimum example that writes local time to the command line at fixed intervals.

#include <cmath>
#include <iostream>
#include <thread>
#include <future>
#include <chrono>

int func(bool & on)
{
    while(on) {
        auto t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
        std::cout << ctime(&t) << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

int main()
{
  bool on = true;
  std::future<int> fi = std::async(std::launch::async, func, on);
  std::this_thread::sleep_for(std::chrono::seconds(5));
  on = false;
  return 0;
}

When the "on" variable is not passed by reference, this code compiles and produces expected result, except that the thread never terminates. As soon as the variable is passed by reference, I receive a compiler error

In file included from /opt/extlib/gcc/5.2.0/gcc/5.2.0/include/c++/5.2.0/thread:39:0,
             from background_thread.cpp:3:
/opt/extlib/gcc/5.2.0/gcc/5.2.0/include/c++/5.2.0/functional: In instantiation of ‘struct std::_Bind_simple<int (*(bool))(bool&)>’:
/opt/extlib/gcc/5.2.0/gcc/5.2.0/include/c++/5.2.0/future:1709:67:   required from ‘std::future<typename std::result_of<_Functor(_ArgTypes ...)>::type> std::async(std::launch, _Fn&&, _Args&& ...) [with _Fn = int (&)(bool&); _Args = {bool&}; typename std::result_of<_Functor(_ArgTypes ...)>::type = int]’
background_thread.cpp:20:64:   required from here
/opt/extlib/gcc/5.2.0/gcc/5.2.0/include/c++/5.2.0/functional:1505:61: error: no type named ‘type’ in ‘class std::result_of<int (*(bool))(bool&)>’
   typedef typename result_of<_Callable(_Args...)>::type result_type;
                                                         ^
/opt/extlib/gcc/5.2.0/gcc/5.2.0/include/c++/5.2.0/functional:1526:9: error: no type named ‘type’ in ‘class std::result_of<int (*(bool))(bool&)>’
     _M_invoke(_Index_tuple<_Indices...>)

Would you be so kind to suggest a way to fix this code?

Bonus Question: What goes wrong, and why does it work with std::ref, but not with a normal &

Aleksejs Fomins
  • 688
  • 1
  • 8
  • 20

4 Answers4

8

std::ref is a start but it's not enough. c++ is only guaranteed to be aware of changes to a variable by another thread if that variable is guarded by either,

a) an atomic, or

b) a memory-fence (mutex, condition_variable, etc)

It is also wise to synchronise the threads before allowing main to finish. Notice that I have a call to fi.get() which will block the main thread until the future is satisfied by the async 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
//    (e.g. 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;
}

example output:

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

by request, an explanation of emit<>(...)

template<class...Ts> void emit(Ts&&...ts)

emit is a function which returns void and takes any number of parameters by x-value reference (i.e. either a const ref, a ref or an r-value ref). I.e. it will accept anything. This means we can call:

  • emit(foo()) - call with the return value of a function (r-value)

  • emit(x, y, foo(), bar(), "text") - call with two references, 2 r-value-references and a string literal

using expand = int[]; defines a type to be an array of integers of indeterminate length. We're going to use this merely to force the evaluation of expressions when we instantiate an object of type expand. The actual array itself will be discarded by the optimiser - we just want the side-effects of constructing it.

void(expand{ ... }); - forces the compiler to instantiate the array but the void cast tells it that we'll never use the actual array itself.

((std::cout << ts), 0)... - for every parameter (denoted by ts) expand one term in the construction of the array. Remember that the array is integers. cout << ts will return an ostream&, so we use the comma operator to sequentially order the call to ostream<< before we simply evaluate an expression of 0. The zero could actually be any integer. It wouldn't matter. It is this integer which is conceptually stored in the array (which is going to be discarded anyway).

0, - The first element of the array is zero. This caters for the case where someone calls emit() with no arguments. The parameter pack Ts will be empty. If we didn't have this leading zero, the resulting evaluation of the array would be int [] { }, which is a zero-length array, which is illegal in c++.

Further notes for beginners:

everything inside the initialiser list of an array is an expression.

An expression is a sequence of 'arithmetic' operations which results in some object. That object may be an actual object (class instance), a pointer, a reference or a fundamental type like an integer.

So in this context, std::cout << x is an expression computed by calling std::ostream::operator<<(std::cout, x) (or its free function equivalent, depending on what x is). The return value from this expression is always std::ostream&.

Putting the expression in brackets does not change its meaning. It simply forces ordering. e.g. a << b + c means 'a shifted left by (b plus c)' whereas (a << b) + c means 'a shifted left by b, then add c'.

The comma ',' is an operator too. a(), b() means 'call function a and then discard the result and then call function b. The value returned shall be the value returned by b'.

So, with some mental gymnastics, you ought to be able to see that ((std::cout << x), 0) means 'call std::ostream::operator<<(std::cout, x), throw away the resulting ostream reference, and then evaluate the value 0). The result of this expression is 0, but the side-effect of streaming x into cout will happen before we result in 0'.

So when Ts is (say) an int and a string pointer, Ts... will be a typelist like this <int, const char*> and ts... will be effectively <int(x), const char*("Hello world")>

So the expression would expand to:

void(int[] {
0,
((std::cout << x), 0),
((std::cout << "Hello world"), 0),
});

Which in baby steps means:

  1. allocate an array of length 3
  2. array[0] = 0
  3. call std::cout << x, throw away the result, array[1] = 0
  4. call std::cout << "Hello world", throw away the result, array[2] = 0

And of course the optimiser sees that this array is never used (because we didn't give it a name) so it removes all the un-necessary bits (because that's what optimisers do), and it becomes equivalent to:

  1. call std::cout << x, throw away the result
  2. call std::cout << "Hello world", throw away the result
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • 1
    Doesn't this still have the issue that `on` goes out of scope when `main` returns? This means `func` will likely be checking `on` after it is destroyed. You would need to make `on` global or block `main` until `func` returns. (But the original solution also has that issue) – Joris Jun 22 '16 at 08:03
  • 1
    @Joris notice that I have deliberately caused the main thread to block by calling `get` on the future. The code is safe. – Richard Hodges Jun 22 '16 at 08:14
  • @RichardHodges can you elaborate on that void(expand...) magick? Why is first 0 there? – Simia_Dei Jun 22 '16 at 10:32
  • 2
    @Simia_Dei the first 0 is there in case you pass no arguments. It's illegal in c++ to have a zero-length array (not in C). I'll update the answer with details. – Richard Hodges Jun 22 '16 at 10:36
4

I can see two issues with your approach:

  • as The Dark mentions, you are referencing a boolean which might have gone out of scope, because your main exists after setting on = false
  • you should try to make the compiler aware that the value of on may change during execution of the loop. If you don't do this, some compiler optimisations may simply replace the while with an infinite loop.

A simple solution would be to place the flag in global memory:

static std::atomic<bool> on = false;

void func()
{
    while (on)
        /* ... */
} 

int main() 
{
    on = true;
    /* launch thread */
    /* wait */
    on = false;
    return 0;
}
Joris
  • 412
  • 3
  • 8
2

Bonus Question: What goes wrong, and why does it work with std::ref, but not with a normal &

Because std::async and also similar interfaces like std::thread and std::bind take the arguments by value. When you pass a reference by-value, then the referred object is copied as the argument. When you use std::reference_wrapper, the wrapper is copied, and the reference inside remains intact.

In case you're wondering why is it designed to work like this, check out this answer on SO

Community
  • 1
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326
-1

As suggested to by a good guy Simon, one way to do this is using std::ref

#include <cmath>
#include <iostream>
#include <thread>
#include <future>
#include <chrono>
#include <functional>

int func(std::reference_wrapper<bool> on)
{
    while(on) {
        auto t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
        std::cout << ctime(&t) << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

int main()
{
  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;
  return 0;
}
Aleksejs Fomins
  • 688
  • 1
  • 8
  • 20
  • This still does not deal with the atomic access to the `on` variable - it needs either an `atomic` or a `mutex` to correctly control the cross thread read-write access required. – Niall Jun 23 '16 at 07:28