Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Threads: Termination of infinite loop thread in c++

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 &

like image 990
Aleksejs Fomins Avatar asked Jun 22 '16 07:06

Aleksejs Fomins


People also ask

How do you stop an infinite loop in a thread?

The only way to stop a thread asynchronously is the stop() method.

What is used to terminate infinite loop?

In order to come out of the infinite loop, we can use the break statement.

How do threads terminate?

A thread automatically terminates when it returns from its entry-point routine. A thread can also explicitly terminate itself or terminate any other thread in the process, using a mechanism called cancelation.

How do I stop a while loop running infinitely?

When you write a while loop, you need to make the necessary updates in your code to make sure that the loop will eventually stop. An infinite loop is a loop that runs indefinitely and it only stops with external intervention or when a break statement is found. You can stop an infinite loop with CTRL + C .


1 Answers

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
like image 136
Richard Hodges Avatar answered Nov 08 '22 09:11

Richard Hodges