代码之家  ›  专栏  ›  技术社区  ›  Ralf

线程相关的主动对象设计问题(C++ + Boost)

  •  1
  • Ralf  · 技术社区  · 15 年前

    我想要一些关于下面列出的iService类的反馈。据我所知,这种类型的类与“活动对象”模式相关。如果我使用任何相关术语不正确,请原谅/纠正。基本上,使用这个活动对象类的类需要提供一个开始和停止方法来控制一些事件循环。这个事件循环可以用while循环或boost asio等实现。

    这个类负责以非阻塞方式启动一个新线程,以便新线程可以处理事件。它还必须处理所有与清理相关的代码。我首先尝试了一种OO方法,在这种方法中,子类负责重写方法来控制事件循环,但清理是混乱的:在调用stop方法的析构函数中,在调用类没有手动调用stop方法的情况下,会导致纯虚拟函数调用。模板化解决方案似乎更干净:

    template <typename T>
    class IService : private boost::noncopyable
    {
        typedef boost::shared_ptr<boost::thread> thread_ptr;
    public:
    
      IService()
      {
      }
    
      ~IService()
      {
        /// try stop the service in case it's running
        stop();
      }
    
      void start()
      {
        boost::mutex::scoped_lock lock(m_threadMutex);
    
        if (m_pServiceThread && m_pServiceThread->joinable())
        {
          // already running
          return;
        }
    
        m_pServiceThread = thread_ptr(new boost::thread(boost::bind(&IService::main, this)));
    
        // need to wait for thread to start: else if destructor is called before thread has started
    
        // Wait for condition to be signaled and then
        // try timed wait since the application could deadlock if the thread never starts?
        //if (m_startCondition.timed_wait(m_threadMutex, boost::posix_time::milliseconds(getServiceTimeoutMs())))
        //{
        //}
        m_startCondition.wait(m_threadMutex);
    
        // notify main to continue: it's blocked on the same condition var
        m_startCondition.notify_one();
      }
    
      void stop()
      {
        // trigger the stopping of the event loop
        m_serviceObject.stop();
    
        if (m_pServiceThread)
        {
          if (m_pServiceThread->joinable())
          {
            m_pServiceThread->join();
          }
          // the service is stopped so we can reset the thread
          m_pServiceThread.reset();
        }
      }
    
    private:
      /// entry point of thread
      void main()
      {
        boost::mutex::scoped_lock lock(m_threadMutex);
        // notify main thread that it can continue
        m_startCondition.notify_one();
    
        // Try Dummy wait to allow 1st thread to resume???
        m_startCondition.wait(m_threadMutex);
    
        // call template implementation of event loop
        m_serviceObject.start();
      }
    
      /// Service thread
      thread_ptr m_pServiceThread;
      /// Thread mutex
      mutable boost::mutex m_threadMutex;
      /// Condition for signaling start of thread
      boost::condition m_startCondition;
    
      /// T must satisfy the implicit service interface and provide a start and a stop method
      T m_serviceObject;
    };
    

    类可以如下使用:

    class TestObject3
    {
    public:
      TestObject3()
          :m_work(m_ioService),
          m_timer(m_ioService, boost::posix_time::milliseconds(200))
      {
          m_timer.async_wait(boost::bind(&TestObject3::doWork, this, boost::asio::placeholders::error));
      }
    
      void start()
      {
          // simple event loop
          m_ioService.run();
      }
    
      void stop()
      {
          // signal end of event loop
          m_ioService.stop();
      }
    
      void doWork(const boost::system::error_code& e)
      {
          // Do some work here
          if (e != boost::asio::error::operation_aborted)
          {
          m_timer.expires_from_now( boost::posix_time::milliseconds(200) );
          m_timer.async_wait(boost::bind(&TestObject3::doWork, this, boost::asio::placeholders::error));
          }
      }
    
    private:
      boost::asio::io_service m_ioService;
      boost::asio::io_service::work m_work;
      boost::asio::deadline_timer m_timer;
    };
    

    现在我具体的问题是:

    1)增压条件变量的使用是否正确?在我看来,这有点像一个黑客:我想等待线程被启动,所以我在条件变量上等待。然后,在主方法中启动新线程后,我再次等待相同的条件变量,以允许初始线程继续。然后,一旦退出初始线程的start方法,新线程就可以继续。这样行吗?

    2)是否存在操作系统无法成功启动线程的情况?我记得在某个地方读到过这样的事。如果可能的话,我应该对条件变量进行定时等待(就像start方法中注释的那样)?

    3)我知道模板类不能“正确地”实现stop方法,即如果事件循环停止失败,代码将在连接上阻塞(在stop或析构函数中),但我看不到解决这一问题的方法。我想应该由类的用户来确保start和stop方法的正确实现?

    4)我会感激任何其他设计错误、改进等?

    谢谢!

    1 回复  |  直到 15 年前
        1
  •  0
  •   Ralf    15 年前

    最终解决了以下问题:

    1)经过多次测试后,使用条件变量似乎很好。

    2)这个问题还没有出现

    3)模板类实现必须满足要求,使用单元测试 正确性测试

    4)改进

    • 添加了带锁的联接
    • 在生成的线程中捕获异常并在主线程中重新引发以避免崩溃和不释放异常信息
    • 使用boost::system::error_code将错误代码传回调用方
    • 实现对象可设置

    代码:

    template <typename T>
    class IService : private boost::noncopyable
    {
      typedef boost::shared_ptr<boost::thread> thread_ptr;
      typedef T ServiceImpl;
    public:
      typedef boost::shared_ptr<IService<T> > ptr;
    
      IService()
        :m_pServiceObject(&m_serviceObject)
      {
      }
    
      ~IService()
      {
        /// try stop the service in case it's running
        if (m_pServiceThread && m_pServiceThread->joinable())
        {
          stop();
        }
      }
    
      static ptr create()
      {
        return boost::make_shared<IService<T> >();
      }
    
      /// Accessor to service implementation. The handle can be used to configure the implementation object
      ServiceImpl& get() { return m_serviceObject; }
      /// Mutator to service implementation. The handle can be used to configure the implementation object
      void set(ServiceImpl rServiceImpl)
      {
        // the implementation object cannot be modified once the thread has been created
        assert(m_pServiceThread == 0);
        m_serviceObject = rServiceImpl;
        m_pServiceObject = &m_serviceObject;
      }
    
      void set(ServiceImpl* pServiceImpl)
      {
        // the implementation object cannot be modified once the thread has been created
        assert(m_pServiceThread == 0);
    
        // make sure service object is valid
        if (pServiceImpl)
          m_pServiceObject = pServiceImpl; 
      }
    
      /// if the service implementation reports an error from the start or stop method call, it can be accessed via this method
      /// NB: only the last error can be accessed
      boost::system::error_code getServiceErrorCode() const { return m_ecService; }
    
      /// The join method allows the caller to block until thread completion
      void join()
      {
        // protect this method from being called twice (e.g. by user and by stop)
        boost::mutex::scoped_lock lock(m_joinMutex);
        if (m_pServiceThread && m_pServiceThread->joinable())
        {
          m_pServiceThread->join();
          m_pServiceThread.reset();
        }
      }
    
      /// This method launches the non-blocking service
      boost::system::error_code start()
      {
        boost::mutex::scoped_lock lock(m_threadMutex);
    
        if (m_pServiceThread && m_pServiceThread->joinable())
        {
          // already running
          return boost::system::error_code(SHARED_INVALID_STATE, shared_category);
        }
    
        m_pServiceThread = thread_ptr(new boost::thread(boost::bind(&IService2::main, this)));
        // Wait for condition to be signaled
        m_startCondition.wait(m_threadMutex);
    
        // notify main to continue: it's blocked on the same condition var
        m_startCondition.notify_one();
        // No error
        return boost::system::error_code();
      }
    
      /// This method stops the non-blocking service
      boost::system::error_code stop()
      {
        // trigger the stopping of the event loop
        //boost::system::error_code ec = m_serviceObject.stop();
        assert(m_pServiceObject);
        boost::system::error_code ec = m_pServiceObject->stop();
        if (ec)
        {
          m_ecService = ec;
          return ec;
        }
    
        // The service implementation can return an error code here for more information
        // However it is the responsibility of the implementation to stop the service event loop (if running)
        // Failure to do so, will result in a block
        // If this occurs in practice, we may consider a timed join?
        join();
    
        // If exception was thrown in new thread, rethrow it.
        // Should the template implementation class want to avoid this, it should catch the exception
        // in its start method and then return and error code instead
        if( m_exception )
          boost::rethrow_exception(m_exception);
    
        return ec;
      }
    
    private:
      /// runs in it's own thread
      void main()
      {
        try
        {
          boost::mutex::scoped_lock lock(m_threadMutex);
          // notify main thread that it can continue
          m_startCondition.notify_one();
          // Try Dummy wait to allow 1st thread to resume
          m_startCondition.wait(m_threadMutex);
    
          // call implementation of event loop
          // This will block
          // In scenarios where the service fails to start, the implementation can return an error code
          m_ecService = m_pServiceObject->start();
    
          m_exception = boost::exception_ptr();
        } 
        catch (...)
        {
          m_exception = boost::current_exception();
        }
      }
    
      /// Service thread
      thread_ptr m_pServiceThread;
      /// Thread mutex
      mutable boost::mutex m_threadMutex;
      /// Join mutex
      mutable boost::mutex m_joinMutex;
      /// Condition for signaling start of thread
      boost::condition m_startCondition;
    
      /// T must satisfy the implicit service interface and provide a start and a stop method
      T m_serviceObject;
      T* m_pServiceObject;
      // Error code for service implementation errors
      boost::system::error_code m_ecService;
    
      // Exception ptr to transport exception across different threads
      boost::exception_ptr m_exception;
    };
    

    当然欢迎进一步的反馈/批评。