繁体   English   中英

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

[英]Threads: Termination of infinite loop thread in c++

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

#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,但没有正常的&

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& ,因此我们使用逗号运算符来顺序地调用ostream<<然后我们简单地计算一个0的表达式。零实际上可以是任何整数。 没关系。 这个整数在概念上存储在数组中(无论如何都将被丢弃)。

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) ,throw离开生成的ostream引用,然后计算值0)。 这个表达式的结果是0,但是在我们得到0'之前,将x流式传输到cout的副作用。

因此,当Ts是(比方说)一个int和一个字符串指针时, Ts...将是一个像<int, const char*>这样的类型<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. 调用std :: cout << x,抛出结果,array [1] = 0
  4. 调用std :: cout <<“Hello world”,抛出结果,array [2] = 0

当然,优化器看到这个数组从未被使用过(因为我们没有给它命名)所以它删除了所有不必要的位(因为这是优化器所做的),它变得等同于:

  1. 调用std :: cout << x,扔掉结果
  2. 调用std :: cout <<“Hello world”,扔掉结果

我可以看到你的方法有两个问题:

  • 正如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;
}

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

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

如果您想知道它为什么设计为这样工作,请在SO上查看此答案

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