C++的含义/pthread/join 错误信息"what(): Invalid argument"

Meaning of C++/pthread/join error message "what(): Invalid argument"

它是一个更大的应用程序的一部分,一个简单的计时器线程,在尝试完成它之前可以正常工作:

boost::asio::io_service io_service;
shared_ptr<thread> loop;
// it is not initialised here, just shows the idea
boost::asio::deadline_timer timer(io_service, interval);

void Timer::spin() {
    loop = make_shared<thread>(&Timer::run, this);
}
void Timer::unspin() {
    io_service.stop();
    loop.get()->join();
}
void run() {
   timer.async_wait(bind(&Timer::callbackWrapper, this, _1));
   // restart
   io_service.run();    
}

spin中创建的线程理论上在unspin()中终止并加入。如果应用程序以注释掉 join() 结尾,则会出现一条错误消息

terminate called without an active exception

如果我 "decrypted" 很好,意思是 "main thread finished when some other threads are still running"(我不懂 C++ 术语。)

如果依次调用 unspin(),可能会显示以下 spotty 错误消息:

terminate called after throwing an instance of 'std::system_error'
  what():  Invalid argument

这很可能是 thread.join() 抛出的。问题是,我搜索了 Stackexchange,但无法解释其他消息。我意识到问题可能出在一小段代码之外(应用程序相当大,所以我不包括它)。但即使是这样,我只想问第二个错误消息的含义及其可能原因。

某些东西抛出 std::system_error 的可能原因有很多种(使用 EINVAL,如这里,或使用其他一些值)。

您可以:

  1. Google 对于 cppreference.com 上的 std::system_error 实例,或
  2. 指示您的调试器在抛出时中断,然后您可以准确地了解是什么导致抛出异常 — 然后转到该东西的文档并阅读它抛出的原因。

这些消息本身并不神秘,它们只是间接归因于整个 C++ 生态系统的工作方式。是否可以将其设计为调用某些特定的 "terminate" 函数,专门为您忘记加入线程的情况保留?当然。

但这真的很笨重,增加了运行时模块的大小,实际上并没有任何好处。随着您编写更多代码,您将获得经验并开始记住或 "feeling" 什么样的事情会导致什么样的问题。多线程代码中有关 std::system_errorstd::terminate 的任何事情 几乎可以肯定 与对所述线程的不当处理有关。你只是在一段时间后才知道。

在这种情况下你可以记住以下两个解释:

terminate called without an active exception

Something 直接调用了 std::terminate。同样,您可以在库文档中搜索与此相关的表述,并找出与您的代码相关的可能原因。

terminate called after throwing an instance of 'std::system_error'

An exception was thrown,但您的程序中没有 try/catch 捕获它,因此异常系统调用了 std::terminate。同样,您可以研究您正在使用的哪些功能可以抛出 std::system_error,并且还可以考虑在代码中添加一些异常安全性。

当前形式的此代码无法合理调试。这是因为它具有代码未检测到的接口规则(例如不连续调用 spin 两次)。这使得代码很难测试、调试和维护。

它甚至会使代码难以理解,因为这些要求可能没有在代码或文本中的任何地方记录。对于未来:

  1. 让您的代码检测到这些违反其接口要求的行为,以便您可以轻松找到并修复它们。这在这里很容易。 unspin函数可以resetshared_ptrspin 函数可以检查 shared_ptr 是否被取消。

  2. 进行单元测试以测试所有正确和不正确的接口访问,这样您就可以自行调试此代码,并确保对不正确访问模式的检测工作正常。其中一个单元测试应该连续两次调用 spin 并确保它 returns 正确的错误或抛出正确的异常。这也用作记录什么可以和什么不能与 class.

  3. 的接口一起工作的代码。
  4. 如果您遇到无法解决的问题,代码和单元测试可以单独显示问题,您将确切地知道代码的哪个方面出现问题。

这也使代码易于维护。如果有人想重新实现此代码、添加功能或优化它,他们可以进行单元测试以确保他们没有破坏其他代码预期依赖的任何行为。

在我的特定情况下,抛出异常是因为线程已经加入。加入前检查 joinable