什么是通过外部信号停止 std::thread 的有效方法

What is a valid way to stop a std::thread by an external signal?

本文关键字:std thread 方法 有效 外部 信号 什么      更新时间:2023-10-16
这是一个

不符合设计工作的代码,请解释我这里有什么问题(代码简化以使其更具可读性(。

shm_server server;
std::thread s{server};
// some work...
std::cout << "press any key to stop the server";
_getch();
server.stop();
s.join();

看起来我为类的另一个副本调用了一个stop方法shm_server。因为stop()只将std::atomic_bool done;(shm_server成员(设置为 true但我看到线程函数(这是shm_serveroperator()(仍然看到done等于 false .

std::thread只有移动结构器?

在这种典型情况下,如何正确地向服务器发送信号?

class shm_server {
    std::atomic_bool    done;
public:
    shm_server() : done{false} {}
    shm_server(shm_server& ) : done{false} {}
    void operator()() {
       while (!done) {
       //...
       }
    }
    void stop() {
       done = true;
    }
};

你以某种方式通过使shm_server可复制而射中了自己的脚。我相信你这样做是为了摆脱编译器错误,而不是因为该类上的复制语义特别有用。我建议您再次删除该构造函数。如果你真的想要复制语义,让构造函数通过引用const来获取其参数。

class shm_server  // cannot copy or move
{
  std::atomic_bool done_ {};
public:
  void
  operator()()
  {
    this->run();
  }
  void
  run()
  {
    using namespace std::chrono_literals;
    while (!this->done_.load())
      {
        std::clog << std::this_thread::get_id() << " working..." << std::endl;
        std::this_thread::sleep_for(1ms);
      }
  }
  void
  stop()
  {
    this->done_.store(true);
  }
};

为了方便起见,我将调用运算符的逻辑分解到命名函数中。您不需要这样做,但我稍后将在示例中使用它。

现在我们必须以某种方式使 run 成员函数在其自己的线程上执行。如果你写

shm_server server {};
std::thread worker {server};  // won't compile

编译器不会让你这样做,因为它 - 正如你自己推测的那样 - 会尝试复制不可复制的server对象。

@Ben Voigt建议将server对象包装到一个std::reference_wrapper中,该是一个行为类似于引用的对象。

shm_server server {};
std::thread worker {std::ref(server)};

这将按预期工作,并有效地传递指向std::thread构造函数的指针,而不是实际的server对象。

但是,我发现这不是这里最直接的解决方案。您真正想做的是在不同的线程上运行server的成员函数。而std::thread 的构造函数可以让你做到这一点。

shm_server server {};
std::thread worker {&shm_server::run, &server};

在这里,我将 run 成员函数的地址和指向 server 对象的指针(将作为隐式this指针传递(作为参数传递。我引入了run函数,因为语法可能不那么烦人。您也可以直接传递呼叫接线员的地址。

shm_server server {};
std::thread worker {&shm_server::operator(), &server};

这是一个完整的示例。

int
main()
{
  using namespace std::chrono_literals;
  std::clog << std::this_thread::get_id() << " starting up" << std::endl;
  shm_server server {};
  std::thread worker {&shm_server::operator(), &server};
  //std::thread worker {std::ref(server)};                 // same effect
  //std::thread worker {&shm_server::run, &server};        // same effect
  std::this_thread::sleep_for(10ms);
  server.stop();
  worker.join();
  std::clog << std::this_thread::get_id() << " good bye" << std::endl;
}

可能的输出:

140435324311360 starting up
140435306977024 working...
140435306977024 working...
140435306977024 working...
140435306977024 working...
140435306977024 working...
140435306977024 working...
140435306977024 working...
140435306977024 working...
140435306977024 working...
140435306977024 working...
140435324311360 good bye

如果您认为 shm_server 只有在其自己的线程上运行时才有用,您还可以为其提供数据成员std::thread,并使其构造函数(或专用的 start 成员函数,如果您愿意(启动,其析构函数加入线程。

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>
class shm_server
{
  std::atomic_bool done_ {};
  std::thread worker_ {};
public:
  shm_server()
  {
    this->worker_ = std::thread {&shm_server::run_, this};
  }
  ~shm_server()
  {
    this->done_.store(true);
    if (this->worker_.joinable())
      this->worker_.join();
  }
private:
  void
  run_()
  {
    using namespace std::chrono_literals;
    while (!this->done_.load())
      {
        std::clog << std::this_thread::get_id() << " working..." << std::endl;
        std::this_thread::sleep_for(1ms);
      }
  }
};

int
main()
{
  using namespace std::chrono_literals;
  std::clog << std::this_thread::get_id() << " starting up" << std::endl;
  {
    shm_server server {};
    std::this_thread::sleep_for(10ms);
  }
  std::clog << std::this_thread::get_id() << " good bye" << std::endl;
}