草庐IT

c++ - 在类的析构函数中关闭类的线程成员是个好主意吗?

coder 2023-06-01 原文

当需要销毁该类的对象时,关闭由 C++ 类管理的 Boost 线程的最佳方法是什么?我有一个类,它在构造时创建并启动一个线程,并提供一个公共(public) Wake() 方法,该方法在需要做一些工作时唤醒线程。 Wake() 方法使用 Boost mutex 和 Boost 条件变量向线程发出信号;线程过程等待条件变量,然后完成工作并返回等待。

目前,我在类的析构函数中关闭了这个线程,使用 bool 成员变量作为“运行”标志;我清除标志,然后在条件变量上调用 notify_one()。然后线程过程唤醒,注意到“运行”为假,然后返回。代码如下:

class Worker
{
public:
    Worker();
    ~Worker();
    void Wake();
private:
    Worker(Worker const& rhs);             // prevent copying
    Worker& operator=(Worker const& rhs);  // prevent assignment
    void ThreadProc();
    bool m_Running;
    boost::mutex               m_Mutex;
    boost::condition_variable  m_Condition;
    boost::scoped_ptr<boost::thread> m_pThread;
};

Worker::Worker()
    : m_Running(true)
    , m_Mutex()
    , m_Condition()
    , m_pThread()
{
    m_pThread.reset(new boost::thread(boost::bind(&Worker::ThreadProc, this)));
}

Worker::~Worker()
{
    m_Running = false;
    m_Condition.notify_one();
    m_pThread->join();
}

void Worker::Wake()
{
    boost::lock_guard<boost::mutex> lock(m_Mutex);
    m_Condition.notify_one();
}

void Worker::ThreadProc()
{
    for (;;)
    {
        boost::unique_lock<boost::mutex> lock(m_Mutex);
        m_Condition.wait(lock);
        if (! m_Running) break;
        // do some work here
    }
}

像这样关闭类的析构函数中的线程是一个好主意,还是我应该提供一个公共(public)方法让用户在对象被销毁之前执行此操作,当有更多的错误处理和/或强制的可能性时如果线程过程未能干净或及时返回,则销毁线程?

在析构函数中清理我的对象的困惑很有吸引力,因为它不需要用户对细节的关注(抽象,欢呼!)但在我看来,如果我能保证采取完全负责成功彻底地清理事物,并且类外的代码可能有一天需要知道线程是否被干净地关闭。

另外,我使用的机制——写入一个线程堆栈上的对象中的成员变量并在另一个线程中读取该变量——是否安全且理智?

最佳答案

在类被销毁时释放类创建的资源是个好主意,即使其中一个资源是线程。如果资源是通过用户调用显式创建的,例如 Worker::Start() ,那么还应该有明确的方式释放它,比如Worker::Stop() .如果用户不调用 Worker::Stop(),在析构函数中执行清理也是一个好主意。和/或为用户提供实现 RAII 的作用域辅助类。 -成语,调用 Worker::Start()在它的构造函数和 Worker::Stop()在它的析构函数中。但是,如果资源分配是隐式完成的,例如在 Worker构造函数,那么资源的释放也应该是隐式的,而析构函数是该职责的主要候选者。


破坏

让我们看看Worker::~Worker() .一般规则是 not throw exceptions in destructors .如果一个 Worker对象位于从另一个异常中展开的堆栈上,并且 Worker::~Worker()抛出异常,然后 std::terminate()将被调用,杀死应用程序。而Worker::~Worker()没有显式抛出异常,重要的是要考虑到它正在调用的某些函数可能会抛出:

如果 std::terminate()是期望的行为,则无需更改。但是,如果 std::terminate()不需要,然后捕获 boost::thread_interrupted并压制它。

Worker::~Worker()
{
  m_Running = false;
  m_Condition.notify_one();
  try
  {
    m_pThread->join();
  }
  catch ( const boost::thread_interrupted& )
  {
    /* suppressed */ 
  }
}

并发

管理线程可能很棘手。定义函数的确切期望行为很重要,例如 Worker::Wake()。 ,以及了解 boost 线程和同步的类型的行为。例如, boost::condition_variable::notify_one() 如果 boost::condition_variable::wait() 中没有线程被阻塞,则无效.让我们检查 Worker::Wake() 的可能并发路径.

下面是两种情况下并发的粗略尝试:

  • 操作顺序从上到下发生。 (即顶部的操作发生在底部的操作之前。
  • 并发操作写在同一行。
  • <>用于突出显示一个线程何时唤醒或解除阻塞另一个线程。例如 A > B表示线程 A正在解除阻塞线程 B .

场景:Worker::Wake()Worker::ThreadProc() 时调用在 m_Condition 上被阻止.

Other Thread                       | Worker::ThreadProc
-----------------------------------+------------------------------------------
                                   | lock( m_Mutex )
                                   | `-- m_Mutex.lock()
                                   | m_Condition::wait( lock )
                                   | |-- m_Mutex.unlock()
                                   | |-- waits on notification
Worker::Wake()                     | |
|-- lock( m_Mutex )                | |
|   `-- m_Mutex.lock()             | |
|-- m_Condition::notify_one()      > |-- wakes up from notification
`-- ~lock()                        | `-- m_Mutex.lock() // blocks
    `-- m_Mutex.unlock()           >     `-- // acquires lock
                                   | // do some work here
                                   | ~lock() // end of for loop's scope
                                   | `-- m_Mutex.unlock()

Result: Worker::Wake() returns fairly quickly, and Worker::ThreadProc runs.


Scenario: Worker::Wake() invoked while Worker::ThreadProc() is not blocked on m_Condition.

Other Thread                       | Worker::ThreadProc
-----------------------------------+------------------------------------------
                                   | lock( m_Mutex )
                                   | `-- m_Mutex.lock()
                                   | m_Condition::wait( lock )
                                   | |-- m_Mutex.unlock()
Worker::Wake()                     > |-- wakes up
                                   | `-- m_Mutex.lock()
Worker::Wake()                     | // do some work here
|-- lock( m_Mutex )                | // still doing work...
|   |-- m_Mutex.lock() // block    | // hope we do not block on a system call
|   |                              | // and more work...
|   |                              | ~lock() // end of for loop's scope
|   |-- // still blocked           < `-- m_Mutex.unlock()
|   `-- // acquires lock           | lock( m_Mutex ) // next 'for' iteration.
|-- m_Condition::notify_one()      | `-- m_Mutex.lock() // blocked
`-- ~lock()                        |     |-- // still blocked
    `-- m_Mutex.unlock()           >     `-- // acquires lock
                                   | m_Condition::wait( lock )    
                                   | |-- m_Mutex.unlock()
                                   | `-- waits on notification
                                   |     `-- still waiting...

Result: Worker::Wake() blocked as Worker::ThreadProc did work, but was a no-op, as it sent a notification to m_Condition when no one was waiting on it.

This is not particularly dangerous for Worker::Wake(), but it can cause problems in Worker::~Worker(). If Worker::~Worker() runs while Worker::ThreadProc is doing work, then Worker::~Worker() may block indefinitely when joining the thread, as the thread may not be waiting on m_Condition at the point in which it is notified, and Worker::ThreadProc only checks m_Running after it is done waiting on m_Condition.


Working Towards a Solution

In this example, lets define the following requirements:

  • Worker::~Worker() will not cause std::terminate() to be invoked.
  • Worker::Wake() will not block while Worker::ThreadProc is doing work.
  • If Worker::Wake() is called while Worker::ThreadProc is not doing work, then it will notify Worker::ThreadProc to do work.
  • If Worker::Wake() is called while Worker::ThreadProc is doing work, then it will notify Worker::ThreadProc to perform another iteration of work.
  • Multiple calls to Worker::Wake() while Worker::ThreadProc is doing work will result in Worker::ThreadProc performing a single additional iteration of work.

Code:

#include <boost/thread.hpp>
 
class Worker
{
public:
  Worker();
  ~Worker();
  void Wake();
private:
  Worker(Worker const& rhs);             // prevent copying
  Worker& operator=(Worker const& rhs);  // prevent assignment
  void ThreadProc();
 
  enum state { HAS_WORK, NO_WORK, SHUTDOWN };
  
  state                            m_State;
  boost::mutex                     m_Mutex;
  boost::condition_variable        m_Condition;
  boost::thread                    m_Thread;
};
 
Worker::Worker()
  : m_State(NO_WORK)
  , m_Mutex()
  , m_Condition()
  , m_Thread()
{
  m_Thread = boost::thread(&Worker::ThreadProc, this);
}
 
Worker::~Worker()
{
  // Create scope so that the mutex is only locked when changing state and
  // notifying the condition.  It would result in a deadlock if the lock was
  // still held by this function when trying to join the thread.
  {
    boost::lock_guard<boost::mutex> lock(m_Mutex);
    m_State = SHUTDOWN;
    m_Condition.notify_one();
  }
  try { m_Thread.join(); }
  catch ( const boost::thread_interrupted& ) { /* suppress */ };
}
 
void Worker::Wake()
{
  boost::lock_guard<boost::mutex> lock(m_Mutex);
  m_State = HAS_WORK;
  m_Condition.notify_one();
}
 
void Worker::ThreadProc()
{
  for (;;)
  {
    // Create scope to only lock the mutex when checking for the state.  Do
    // not continue to hold the mutex wile doing busy work.
    {
      boost::unique_lock<boost::mutex> lock(m_Mutex);
      // While there is no work (implies not shutting down), then wait on
      // the condition.
      while (NO_WORK == m_State)
      {
        m_Condition.wait(lock);
        // Will wake up from either Wake() or ~Worker() signaling the condition
        // variable.  At that point, m_State will either be HAS_WORK or
        // SHUTDOWN.
      }
      // On shutdown, break out of the for loop.
      if (SHUTDOWN == m_State) break;
      // Set state to indicate no work is queued.
      m_State = NO_WORK;
    }
 
    // do some work here
  }
}

注意:根据个人喜好,我选择不分配 boost::thread在堆上,因此,我不需要通过 boost::scoped_ptr 管理它. boost::thread有一个 default constructor这将引用 Not-a-Thread,它是 move-assignable .

关于c++ - 在类的析构函数中关闭类的线程成员是个好主意吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11223119/

有关c++ - 在类的析构函数中关闭类的线程成员是个好主意吗?的更多相关文章

  1. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  2. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

    我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

  3. ruby-on-rails - 在 ruby​​ 中使用 gsub 函数替换单词 - 2

    我正在尝试用ruby​​中的gsub函数替换字符串中的某些单词,但有时效果很好,在某些情况下会出现此错误?这种格式有什么问题吗NoMethodError(undefinedmethod`gsub!'fornil:NilClass):模型.rbclassTest"replacethisID1",WAY=>"replacethisID2andID3",DELTA=>"replacethisID4"}end另一个模型.rbclassCheck 最佳答案 啊,我找到了!gsub!是一个非常奇怪的方法。首先,它替换了字符串,所以它实际上修改了

  4. ruby-on-rails - Rails 模型——非持久类成员或属性? - 2

    对于Rails模型,是否可以/建议让一个类的成员不持久保存到数据库中?我想将用户最后选择的类型存储在session变量中。由于我无法从我的模型中设置session变量,我想将值存储在一个“虚拟”类成员中,该成员只是将值传递回Controller。你能有这样的类(class)成员吗? 最佳答案 将非持久属性添加到Rails模型就像任何其他Ruby类一样:classUser扩展解释:在Ruby中,所有实例变量都是私有(private)的,不需要在赋值前定义。attr_accessor创建一个setter和getter方法:classUs

  5. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  6. 通过 MacPorts 的 RubyGems 是个好主意吗? - 2

    从MB升级到新的MBP后,Apple的迁移助手没有移动我的gem。我这次是通过macports安装ruby​​gems,希望在下次升级时避免这种情况。有什么我应该注意的陷阱吗? 最佳答案 如果你想把你的gems安装在你的主目录中(在传输过程中应该复制过来,作为一个附带的好处,会让你以你自己的身份运行geminstall,而不是root),将gemhome:键设置为您在~/.gemrc中的主目录中的路径. 关于通过MacPorts的RubyGems是个好主意吗?,我们在StackOverf

  7. ruby-on-rails - 使用 config.threadsafe 时从 lib/加载模块/类的正确方法是什么!选项? - 2

    我一直致力于让我们的Rails2.3.8应用程序在JRuby下正确运行。一切正常,直到我启用config.threadsafe!以实现JRuby提供的并发性。这导致lib/中的模块和类不再自动加载。使用config.threadsafe!启用:$rubyscript/runner-eproduction'pSim::Sim200Provisioner'/Users/amchale/.rvm/gems/jruby-1.5.1@web-services/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:105:in`co

  8. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  9. 没有类的 Ruby 方法? - 2

    大家好!我想知道Ruby中未使用语法ClassName.method_name调用的方法是如何工作的。我头脑中的一些是puts、print、gets、chomp。可以在不使用点运算符的情况下调用这些方法。为什么是这样?他们来自哪里?我怎样才能看到这些方法的完整列表? 最佳答案 Kernel中的所有方法都可用于Object类的所有对象或从Object派生的任何类。您可以使用Kernel.instance_methods列出它们。 关于没有类的Ruby方法?,我们在StackOverflow

  10. ruby - Rails 关联 - 同一个类的多个 has_one 关系 - 2

    我的问题的一个例子是体育游戏。一场体育比赛有两支球队,一支主队和一支客队。我的事件记录模型如下:classTeam"Team"has_one:away_team,:class_name=>"Team"end我希望能够通过游戏访问一个团队,例如:Game.find(1).home_team但我收到一个单元化常量错误:Game::team。谁能告诉我我做错了什么?谢谢, 最佳答案 如果Gamehas_one:team那么Rails假设您的teams表有一个game_id列。不过,您想要的是games表有一个team_id列,在这种情况下

随机推荐