简体   繁体   English

线程:在c ++中终止无限循环线程

[英]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. 当“on”变量未通过引用传递时,此代码将编译并生成预期结果,但线程永远不会终止。 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 & 奖金问题:出了什么问题,为什么它适用于std :: ref,但没有正常的&

std::ref is a start but it's not enough. std::ref是一个开始,但还不够。 c++ is only guaranteed to be aware of changes to a variable by another thread if that variable is guarded by either, 只有当该变量被任何一个线程保护时,c ++才能保证知道另一个线程对变量的更改,

a) an atomic, or a)原子,或

b) a memory-fence (mutex, condition_variable, etc) b)内存栅栏(互斥,condition_variable等)

It is also wise to synchronise the threads before allowing main to finish. 在允许main完成之前同步线程也是明智的。 Notice that I have a call to fi.get() which will block the main thread until the future is satisfied by the async thread. 请注意,我调用了fi.get() ,它将阻塞主线程,直到异步线程满足未来。

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<>(...) 根据要求, 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 (ie either a const ref, a ref or an r-value ref). emit是一个函数,它返回void并通过x值引用获取任意数量的参数(即const ref,ref或r-value ref)。 Ie it will accept anything. 即它会接受任何东西。 This means we can call: 这意味着我们可以致电:

  • emit(foo()) - call with the return value of a function (r-value) emit(foo()) - 使用函数的返回值调用(r值)

  • emit(x, y, foo(), bar(), "text") - call with two references, 2 r-value-references and a string literal emit(x, y, foo(), bar(), "text") - 使用两个引用调用,2个r值引用和一个字符串文字

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 . 当我们实例化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. - 强制编译器实例化数组,但void cast告诉它我们永远不会使用实际的数组本身。

((std::cout << ts), 0)... - for every parameter (denoted by ts) expand one term in the construction of the array. ((std::cout << ts), 0)... - 对于每个参数(用ts表示),在数组结构中展开一个项。 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. cout << ts将返回一个ostream& ,因此我们使用逗号运算符来顺序地调用ostream<<然后我们简单地计算一个0的表达式。零实际上可以是任何整数。 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. 0, - 数组的第一个元素为零。 This caters for the case where someone calls emit() with no arguments. 这适用于有人调用没有参数的emit()的情况。 The parameter pack Ts will be empty. 参数包Ts将为空。 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++. 如果我们没有这个前导零,那么对数组的结果评估将是int [] { } ,这是一个零长度数组,在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). 所以在这个上下文中, std::cout << x是一个通过调用std::ostream::operator<<(std::cout, x)计算的表达式(或者它的自由函数等价,取决于x是什么)。 The return value from this expression is always std::ostream& . 此表达式的返回值始终为std::ostream&

Putting the expression in brackets does not change its meaning. 将表达式放在括号中不会改变其含义。 It simply forces ordering. 它只是强迫订购。 eg a << b + c means 'a shifted left by (b plus c)' whereas (a << b) + c means 'a shifted left by b, then add c'. 例如a << b + c表示'向左移位(b加c)',而(a << b) + c表示'向左移位b,然后加c'。

The comma ',' is an operator too. 逗号','也是一个运算符。 a(), b() means 'call function a and then discard the result and then call function b. a(), b()表示'调用函数a然后丢弃结果然后调用函数b。 The value returned shall be the value returned by b'. 返回的值应为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). 所以,对于一些心理体操,你应该能够看到((std::cout << x), 0)意味着'调用std::ostream::operator<<(std::cout, x) ,throw离开生成的ostream引用,然后计算值0)。 The result of this expression is 0, but the side-effect of streaming x into cout will happen before we result in 0'. 这个表达式的结果是0,但是在我们得到0'之前,将x流式传输到cout的副作用。

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")> 因此,当Ts是(比方说)一个int和一个字符串指针时, Ts...将是一个像<int, const char*>这样的类型<int, const char*>ts...将是有效的<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 分配一个长度为3的数组
  2. array[0] = 0 array [0] = 0
  3. call std::cout << x, throw away the result, array[1] = 0 调用std :: cout << x,抛出结果,array [1] = 0
  4. call std::cout << "Hello world", throw away the result, array[2] = 0 调用std :: cout <<“Hello world”,抛出结果,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 调用std :: cout << x,扔掉结果
  2. call std::cout << "Hello world", throw away the result 调用std :: cout <<“Hello world”,扔掉结果

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 正如The Dark提到的那样,你引用的是一个可能超出范围的布尔值,因为你的main在设置为on = false后存在
  • you should try to make the compiler aware that the value of on may change during execution of the loop. 你应该尝试让编译器意识到在执行循环期间on的值可能会改变。 If you don't do this, some compiler optimisations may simply replace the while with an infinite loop. 如果不这样做,一些编译器优化可能只是用无限循环替换while。

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;
}

Bonus Question: What goes wrong, and why does it work with std::ref, but not with a normal & 奖金问题:出了什么问题,为什么它适用于std :: ref,但没有正常的&

Because std::async and also similar interfaces like std::thread and std::bind take the arguments by value. 因为std::async以及类似std::threadstd::bind类似接口都是按值获取参数。 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. 当您使用std::reference_wrapper ,将复制包装器,并且内部的引用保持不变。

In case you're wondering why is it designed to work like this, check out this answer on SO 如果您想知道它为什么设计为这样工作,请在SO上查看此答案

As suggested to by a good guy Simon, one way to do this is using std::ref 正如一个好人Simon所建议的,一种方法是使用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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM