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

时间:2016-06-22 07:03:39

标签: c++ multithreading infinite-loop

我正在尝试编写一个可以在我的主程序后台运行并监视某个线程的线程。在某些时候,主程序应该通知线程安全退出。以下是以固定间隔将本地时间写入命令行的最小示例。

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

当“on”变量未通过引用传递时,此代码将编译并生成预期结果,但线程永远不会终止。一旦变量通过引用传递,我就会收到编译器错误

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...>)

您是否愿意建议修复此代码的方法?

奖金问题:出了什么问题,为什么它适用于std :: ref,但没有正常&amp;

4 个答案:

答案 0 :(得分:5)

std::ref是一个开始,但还不够。只有当该变量被任何一个线程保护时,c ++才能保证知道另一个线程对变量的更改,

a)原子,或

b)内存栅栏(互斥,condition_variable等)

在允许main完成之前同步线程也是明智的。请注意,我调用fi.get()将阻塞主线程,直到异步线程满足未来。

更新代码:

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

示例输出:

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

按要求,emit<>(...)

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

emit是一个函数,它返回void并通过x值引用获取任意数量的参数(即const ref,ref或r-value ref)。即它会接受任何东西。这意味着我们可以致电:

  • emit(foo()) - 使用函数的返回值调用(r值)

  • emit(x, y, foo(), bar(), "text") - 使用两个引用调用,2个r值引用和一个字符串文字

using expand = int[];将类型定义为不确定长度的整数数组。当我们实例化expand类型的对象时,我们将仅使用它来强制评估表达式。实际的数组本身将被优化器丢弃 - 我们只需要副作用来构建它。

void(expand{ ... }); - 强制编译器实例化数组,但void cast告诉它我们永远不会使用实际的数组本身。

((std::cout << ts), 0)... - 对于每个参数(用ts表示),在数组构造中展开一个项。请记住,数组是整数。 cout << ts将返回ostream&,因此我们使用逗号运算符在我们简单地计算表达式0之前按顺序将调用排序到ostream<<。零实际上可以是任何整数。没关系。这个整数在概念上存储在数组中(无论如何都将被丢弃)。

0, - 数组的第一个元素为零。这适用于有人在没有参数的情况下调用emit()的情况。参数包Ts将为空。如果我们没有这个前导零,那么对数组的结果评估将是int [] { },这是一个零长度数组,在c ++中是非法的。

初学者的进一步说明:

数组初始化列表中的所有内容都是表达式

表达式是一系列“算术”运算,它们会产生一些对象。该对象可以是实际对象(类实例),指针,引用或类似整数的基本类型。

因此,在此上下文中,std::cout << x是通过调用std::ostream::operator<<(std::cout, x)(或其自由函数等效,取决于x是什么)计算的表达式。此表达式中的返回值始终为std::ostream&

将表达式放在括号中不会改变其含义。它只是强迫订购。例如a << b + c表示'向左移位(b加c)',而(a << b) + c表示'向左移位b,然后加c'。

逗号','也是一个运算符。 a(), b()表示'调用函数a然后丢弃结果然后调用函数b。返回的值应为b'返回的值。

因此,对于一些心理体操,您应该能够看到((std::cout << x), 0)表示'调用std::ostream::operator<<(std::cout, x),丢弃生成的ostream引用,然后评估值0)。这个表达式的结果是0,但是在我们得到0'之前,将x流式传输到cout的副作用。

因此当Ts是(比方说)一个int和一个字符串指针时,Ts...将是一个类似于<int, const char*>的类型列表,而ts...将是有效的<int(x), const char*("Hello world")> }

因此表达式将扩展为:

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

在婴儿步骤中意味着:

  1. 分配长度为3的数组
  2. array [0] = 0
  3. call std :: cout&lt;&lt; x,抛弃结果,array [1] = 0
  4. call std :: cout&lt;&lt; “Hello world”,扔掉结果,数组[2] = 0
  5. 当然,优化器看到这个数组从未被使用过(因为我们没有给它命名)所以它删除了所有不必要的位(因为这是优化器所做的),它变得等同于:< / p>

    1. call std :: cout&lt;&lt; x,扔掉结果
    2. call std :: cout&lt;&lt; “Hello world”,扔掉结果

答案 1 :(得分:4)

我可以看到您的方法存在两个问题:

  • 正如The Dark提到的那样,你引用了一个可能超出范围的布尔值,因为设置main后你的on = false存在
  • 您应该尝试让编译器知道在执行循环期间on的值可能会发生变化。如果不这样做,一些编译器优化可能只是用无限循环替换while。

一个简单的解决方案是将标志放在全局内存中:

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

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

int main() 
{
    on = true;
    /* launch thread */
    /* wait */
    on = false;
    return 0;
}

答案 2 :(得分:2)

  

奖金问题:出了什么问题,为什么它与std :: ref一起使用,而不是正常&amp;

因为std::async以及std::threadstd::bind之类的类似接口会按值获取参数。传递引用按值时,将引用的对象复制为参数。当您使用std::reference_wrapper时,将复制包装器,并且内部的引用保持不变。

如果您想知道为什么它的设计可以像这样工作,请查看this answer on SO

答案 3 :(得分:-1)

正如一个好人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;
}