线程: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 值引用(即常量引用、引用或 r 值引用)获取任意数量的参数。 即它将接受任何东西。这意味着我们可以调用:
emit(foo())
- 使用函数的返回值(r 值)进行调用emit(x, y, foo(), bar(), "text")
- 使用两个引用、2 个 r 值引用和一个字符串文本调用
using expand = int[];
将类型定义为长度不确定的整数数组。我们将仅使用它来强制计算表达式,当我们实例化类型为expand
的对象时。实际的数组本身将被优化器丢弃 - 我们只想要构造它的副作用。
void(expand{ ... });
- 强制编译器实例化数组,但 void 强制转换告诉它我们永远不会使用实际数组本身。
((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
的意思是"a向左移动(b加c)",而(a << b) + c
的意思是"a向左移动b,然后加上c"。
逗号","也是一个运算符。a(), b()
表示"调用函数 A,然后丢弃结果,然后调用函数 B。返回的值应为 b' 返回的值。
因此,通过一些心理体操,您应该能够看到((std::cout << x), 0)
的意思是"调用std::ostream::operator<<(std::cout, x)
,丢弃生成的 ostream 引用,然后计算值 0)。此表达式的结果为 0,但将 x 流式传输到cout
的副作用将在我们产生 0' 之前发生。
因此,当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),
});
在婴儿步骤中意味着:
- 分配长度为 3 的数组
- 数组[0] = 0
- 调用 std::cout <<x,丢弃结果,array[1] = 0
- 调用 std::cout <<"Hello world",扔掉结果,array[2] = 0
当然,优化器看到这个数组从未被使用过(因为我们没有给它一个名字),所以它删除了所有不必要的位(因为这就是优化器所做的),它变得等效于:
- 调用 std::cout <<x,扔掉结果
- 调用 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::thread
和std::bind
按值获取参数。传递引用 by-value 时,引用的对象将作为参数进行复制。使用std::reference_wrapper
时,将复制包装器,并且内部的引用保持不变。
如果您想知道为什么它被设计成这样工作,请查看 SO 上的这个答案
正如一个好人西蒙所建议的那样,一种方法是使用 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;
}
- 当我在其中一个线程执行中(在activemq-cpp中)捕获到特定值时,我如何终止/停止所有其他线程
- pthread_kill() 与 pthread_cancel() 终止因 I/O 而阻塞的线程
- 终止读取时阻止的线程 c++11
- 如何安全地终止线程?(使用指针)C++
- 终止调用本机代码的 .Net 线程
- 当加入 C++11 函数的线程仍未终止时,是否可以返回?
- 自终止线程.使用联接或分离
- 终止处理程序在哪个线程中调用?
- 线程不在 Linux 上终止,但在 Mac 上终止
- 在C 中,可以检测到线程的意外终止
- 从c/c 中终止线程中的线程,而无需线柄
- 必须将 std::thread 加入 std::vector<std::thread> 两次以避免从线程 dtor 终止
- 线程终止
- 提升线程终止程序
- 连接线程(阻塞调用线程直到线程终止)和普通函数调用之间的区别是什么
- 是否有任何 pthread 函数可以在最后一个线程终止时调用某些内容
- 线程终止/退出后正在验证数据结构
- 从另一个线程终止C++中的一个线程
- 将一个线程(卡住的)从另一个线程终止
- 带有等待窗口的C++线程终止