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

这是一个不按设计工作的代码请解释我这里有什么问题(简化代码使其更具可读性).

shm_server server;

std::thread s{server};

// some work...

std::cout << "press any key to stop the server";
_getch();
server.stop();
s.join();

看起来我为另一个shm_server类副本调用了stop方法.
因为stop()只设置std :: atomic_bool; (shm_server成员)为true但我看到线程函数(这是shm_server的operator())仍然看到完成等于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

编译器不会让你这样做 – 正如你自己猜测的那样 – 会尝试复制不可复制的服务器对象.

@Ben Voigt具有suggested以将服务器对象包装到std::reference_wrapper中,该对象的行为类似于引用.

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

这按预期工作,并有效地将指针传递给std :: thread构造函数,而不是实际的服务器对象.

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

shm_server server {};
std::thread worker {&shm_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;
}
点赞