• Boost.ASIO简要分析-4 多线程


    4. 多线程

    一般情况下,服务端开启一条线程做io_service::run()工作就足够了。但是,有些情况下可能会变得很糟糕。

    从之前的分析,我们知道异步操作的一个关键步骤就是io_service回调我们注册的handler。现在假设客户端与服务端建立了四个socket连接,相应的I/O对象分别为socket1, socket2, socket3, socket4,并且假设io_service现在正在处理socket1注册的handler。如果这个handler处理的过程很长,那么在这期间socket2,socket3,socket4注册的handler会一直得不到执行,造成不良的使用体验。

    针对这个问题,解决之道只有采用多线程的方法。多线程的用法很简单,我们只要把线程函数boost::asio::io_service::run和io_service指针绑定好传给boost::thread类就好了。如下所示:

    boost::thread t(boost::bind(&boost::asio::io_service::run, &io));
    
    t.join();

    但是,引入多线程又会引入多线程同步的问题,如果这个问题没解决好,死机就是家常便饭了。幸好,asio给我们提供了strand这个类(当然,也可以使用mutex,但是使用strand会使代码更加优雅)。下面,简单介绍下strand这个类。

    1) 用法

    用法很简单,首先定义下变量。

    boost::asio::io_service::strand strand_(&io); //注意io_service对象地址作为他的参数。

    然后在注册回调函数时,在外面套上一层strand_.wrap()就好了,如下所示:

    timer1_.async_wait(strand_.wrap(boost::bind(&printer::print1, this)));
    timer2_.async_wait(strand_.wrap(boost::bind(&printer::print2, this)));
    这样的话,这两个异步操作的回调函数肯定是被顺序执行的。

    2) 源码分析

    在分析源码之前,我们看下一个完整的调用堆栈:

    图片1

    我们把不采用strand时的调用堆栈图拿来比对下

    图片2

    不知道有没有被吓一跳,采用strand方式竟然会多出这么多层调用,让回调的路途看上去如此漫长。

    好了,废话不多说,我们strand的那张调用堆栈图中寻找strand的蛛丝马迹。回调的路上这个函数boost::asio::io_service::strand::dispatch,顿时眼前一亮,让我想起strand类中的dispatch函数。眼尖的朋友可能发现调用堆栈上出现了两次boost::asio::io_service::strand::dispatch,不要奇怪,这两次的handler是不一样的,如下图。

    这是先被调用的

    wps47EF.tmp

    这是后被调用的

    wps47FF.tmp

    下面贴出io_service::strand类:

    class io_service::strand
    {
    public:
      explicit strand(boost::asio::io_service& io_service)
        : service_(boost::asio::use_service<
            boost::asio::detail::strand_service>(io_service))
      {
        service_.construct(impl_);
      }
    
      ~strand()
      {
      }
    
      boost::asio::io_service& get_io_service()
      {
        return service_.get_io_service();
      }
    
    /* 这就是第一个出现在回调路上的函数。
       这个函数的作用是让strand执行给定的handler。
       还有一点要说的就是,如果当前线程调用了service::run,那么该线程可以直接调用handler。这也是和post的区别之一。我们可以假想下如果回调的路上不是strand::dispatch,而是strand::post,那么我们的线程栈上也不一定会这么长了。*/
      template <typename CompletionHandler>
      BOOST_ASIO_INITFN_RESULT_TYPE(CompletionHandler, void ())
      dispatch(BOOST_ASIO_MOVE_ARG(CompletionHandler) handler)
      {
        // If you get an error on the following line it means that your handler does
        // not meet the documented type requirements for a CompletionHandler.
        BOOST_ASIO_COMPLETION_HANDLER_CHECK(CompletionHandler, handler) type_check;
    
        detail::async_result_init<
          CompletionHandler, void ()> init(
            BOOST_ASIO_MOVE_CAST(CompletionHandler)(handler));
    
    // 注意此处是service_是boost::asio::detail::strand_service类型的哦;
    // strand_service里面才是真正控制多线程安全的地方。
        service_.dispatch(impl_, init.handler);
    
        return init.result.get();
      }
    
    // 和dispatch都有投递任务的作用。只是post会马上返回,handler会被某个调用service::run的线程执行。
      template <typename CompletionHandler>
      BOOST_ASIO_INITFN_RESULT_TYPE(CompletionHandler, void ())
      post(BOOST_ASIO_MOVE_ARG(CompletionHandler) handler)
      {
        // If you get an error on the following line it means that your handler does
        // not meet the documented type requirements for a CompletionHandler.
        BOOST_ASIO_COMPLETION_HANDLER_CHECK(CompletionHandler, handler) type_check;
    
        detail::async_result_init<
          CompletionHandler, void ()> init(
            BOOST_ASIO_MOVE_CAST(CompletionHandler)(handler));
    
        service_.post(impl_, init.handler);
    
        return init.result.get();
      }
    
    // 这个wrap函数就是上面说起的那个打包函数。有了它,io_service会先调用strand然后再调用handler,相当于在回调的路上设置了一道关卡,通过strand保证线程安全性。
      template <typename Handler>
    #if defined(GENERATING_DOCUMENTATION)
      unspecified
    #else
      detail::wrapped_handler<strand, Handler, detail::is_continuation_if_running>
    #endif
      wrap(Handler handler)
      {
        return detail::wrapped_handler<io_service::strand, Handler,
            detail::is_continuation_if_running>(*this, handler);
      }
    
      bool running_in_this_thread() const
      {
        return service_.running_in_this_thread(impl_);
      }
    
    private:
      boost::asio::detail::strand_service& service_;
      boost::asio::detail::strand_service::implementation_type impl_;
    };

    光这个类是看不出具体实现细节的,相要了解更多实现细节需要分析strand_service这个类。

    具体的多线程控制方面,我们可以看下strand_service::strand_impl这个嵌套类

    // The underlying implementation of a strand.
    
    class strand_impl
    
        : public operation
    
      {
    
    public:
    
    strand_impl();
    
    private:
    
    // Only this service will have access to the internal values.
    
    friend class strand_service;
    
    friend struct on_do_complete_exit;
    
    friend struct on_dispatch_exit;
    
    // 这就是那个用来多线程控制的互斥锁
    
    // Mutex to protect access to internal data.
    
    boost::asio::detail::mutex mutex_;
    
    // 用来表示strand是否被某个handler“锁住”的变量
    
    // Indicates whether the strand is currently "locked" by a handler. This
    
    // means that there is a handler upcall in progress, or that the strand
    
    // itself has been scheduled in order to invoke some pending handlers.
    
    bool locked_;
    
    // 哦,等待处理的排队队列
    
    // The handlers that are waiting on the strand but should not be run until
    
    // after the next time the strand is scheduled. This queue must only be
    
    // modified while the mutex is locked.
    
    op_queue<operation> waiting_queue_;
    
    // 已经获取锁并准备运行的handler
    
    // The handlers that are ready to be run. Logically speaking, these are the
    
    // handlers that hold the strand's lock. The ready queue is only modified
    
    // from within the strand and so may be accessed without locking the mutex.
    
    op_queue<operation> ready_queue_;
    
      };

    可以看出mutex_、locked_、waiting_queue_、ready_queue_这四个变量保证了线程安全性。具体实现方法,可以自己去调试下,这里就不细细分析了(其实是肚子饿了,要去吃饭了^^)。

    PS: 异步调用+多线程肯定会大大增加你的调试复杂度,加上日志记录是势在必行的事情。这里向大家推荐下简单易用的glog:https://github.com/google/glog

  • 相关阅读:
    ReentrantLock与synchronized的差别
    读TIJ -1 对象入门
    wikioi 2573 大顶堆与小顶堆并用
    开源 免费 java CMS
    UVA10972
    springboot5
    spring-boot4
    spring-boot3
    spring-boot2
    spring-boot1
  • 原文地址:https://www.cnblogs.com/SudoSky/p/4523971.html
Copyright © 2020-2023  润新知