不落辰

知不可乎骤得,托遗响于悲风

0%

bind与shared_ptr

bind : 传递值. 会导致shared_ptr的引用计数++
muduo中TCPConnectionPtr的生命周期介绍

bind 只传递值 而非 引用

  • 在函数传参时,可能发生shared_ptr的拷贝,造成引用计数++

    • pass by value : 如果传入的是个左值,会发生拷贝构造,引用计数++;如果传入的是个右值(即临时构造的shared_ptr),那么发生移动构造,引用计数不变
    • pass by reference 如const& : 不拷贝,引用计数不变
    • pass by value : 即函数的形参是一个非引用或者指针。pass by reference : 即函数的形参是一个引用&。
  • 那么,bind class的成员函数时,由于要传入一个class对象的指针

    • 这是一个pass by value的过程
    • 我们绑定上去的指针可以是shared_ptr/裸指针,但不能是weak_ptr
    • 如果是以左值传入shared_ptr对象,那么引用计数++
    • 如果是以右值传入shared_ptr临时量 / 或者传入裸指针,那么引用计数当然不变
  • bind –> tuple

    • 只能pass by value,而不能pass by reference!!!!
    • pass by value : 如果传入的是个左值,会发生拷贝构造 ; 如果传入的是个右值,那么发生移动构造
    • 哪怕这个函数的形参就是个 引用&,但是我们如果绑定的话,绑定上去的对象也是会进行一个拷贝 然后赋值给形参!!(与直觉不同,因为我们正常形参是引用的话那么传入的也应当是一个引用,但是这里bind绑定的其实是一个值)(见f6 f7)
  • testCode.cpp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    shared_from_this() 返回的是一个this的shared_ptr的临时量
    class TestClass : public std::enable_shared_from_this<TestClass>
    {
    public:
    using call_back = function<void(void)>;
    void hello()
    {
    cout<<"hello"<<endl;
    }
    void testFunc()
    {
    cout<<"========testFunc start========"<<endl;
    // shared_from_this() return a 临时的 shared_ptr,引用计数++。如果不用变量保存下来的话,这条语句过了之后就--。也就是只有这一条语句的use_count变了+1
    cout<<"+ shared_from_this() " << shared_from_this().use_count() <<endl;
    cout<<"========testFunc end========"<<endl;
    }
    void testBind()
    {
    cout<<"=======testBind start=========="<<endl;
    cb_ = std::bind(&TestClass::hello,shared_from_this()); // ++
    cb_();
    cout<<"bind + shared_from_this() = "<<shared_from_this().use_count()<<endl; // ++ --
    cout<<"=======testBind end=========="<<endl;
    }

    void testPassAsValue(shared_ptr<TestClass> p) // ++
    {
    cout<<"============testPassAsValue start============"<<endl;
    cout<<p.use_count()<<endl;
    cout<<"============testPassAsValue end============"<<endl;
    }

    void testPassAsRef(const shared_ptr<TestClass>& p)
    {
    cout<<"==========testPassAsRef start=============="<<endl;
    cout<<p.use_count()<<endl;
    cout<<"==========testPassAsRef end================"<<endl;
    }

    ~TestClass(){
    cout<<"~TestClass()"<<endl;
    }
    private:
    call_back cb_;
    };

    int main()
    {
    std::shared_ptr<TestClass> sp(new TestClass());
    cout<<sp.use_count()<<endl; // 1
    std::weak_ptr<TestClass> wp(sp);
    cout<<sp.use_count()<<endl; // 1


    cout<<"********************************"<<endl;
    // bind 绑定指针 是传值 ,会导致引用计数 ++
    std::bind(&TestClass::testFunc,sp)(); // 3 = 1(sp) + 1(bind) + 1 (shared_from_this())
    cout<<sp.use_count()<<endl; // 1

    cout<<"********************************"<<endl;
    function<void(void)> f1 = std::bind(&TestClass::testBind,sp);
    cout<<sp.use_count()<<endl; // 2 = 1(sp) + 1(f1 bind)
    f1(); // 4 = 1(sp) + 1(bind) + 1(bind) + 1(shared_from_this())
    cout<<sp.use_count()<<endl; // 3 = 1(sp) + 1(f1 bind) + 1(cb_ bind)

    cout<<"********************************"<<endl;
    function<void(shared_ptr<TestClass>)> f2 = std::bind(&TestClass::testPassAsValue,sp,std::placeholders::_1); // 4 = 1(sp) + 1(f1 bind) + 1(cb_ bind) + 1(f2 bind)
    cout<<sp.use_count()<<endl; // 4

    f1(); // 5

    f2(sp); // 5

    cout<<"********************************"<<endl;

    function<void(const shared_ptr<TestClass>& p)> f3 = std::bind(&TestClass::testPassAsRef,sp,std::placeholders::_1);
    cout<<sp.use_count()<<endl; // 5 = 4 + 1(f3 bind)
    f3(sp); // 5 = 4 + 1(f3 bind) 传引用不是传值 不会导致引用计数++

    cout<<"********************************"<<endl;

    function<void(void)> f4 = std::bind(&TestClass::testPassAsValue,sp,sp);
    cout<<sp.use_count()<<endl; // 7 = 5 + 1(f4 bind) +1(f4 bind)

    // pass by value but 传入右值
    cout<<"********************************"<<endl;
    function<void(void)> f5 = std::bind(&TestClass::testFunc, shared_ptr<TestClass>(new TestClass())); // 移动构造
    f5(); // 2 = bind (临时量移动构造给别人) + shared_from_this()


    cout<<"*******************************************"<<endl;

    // 下面证明了bind只能传递值
    **哪怕这个函数的形参就是个 引用&,但是我们如果绑定的话,绑定上去的对象也是会进行一个拷贝 然后赋值给形参!!**

    shared_ptr<TestClass> p(new TestClass());
    function<void(void)> f6 = std::bind(&TestClass::testPassAsRef,p,p);
    cout<<p.use_count()<<endl; // 3 = 1(define) + 1(bind) + 1(bind bind只能传值 )
    f6(); // 3
    cout<<"*******************************************"<<endl;
    shared_ptr<TestClass> p2(new TestClass());
    function<void(const shared_ptr<TestClass> & )> f7 = std::bind(&TestClass::testPassAsRef,p2,std::placeholders::_1);
    cout<<p2.use_count()<<endl; // 2 = 1(define) + 1(bind)
    f7(p2); // 2 = 1(define) + 1(bind) 与上面的区别在于 bind 只能传值 而 这个形参 我们在调用的时候 是可以直接填引用进去的
    }

    1
    1
    ********************************
    ========testFunc start========
    + shared_from_this() 3
    ========testFunc end========
    1
    ********************************
    2
    =======testBind start==========
    hello
    bind + shared_from_this() = 4
    =======testBind end==========
    3
    ********************************
    4
    =======testBind start==========
    hello
    bind + shared_from_this() = 5
    =======testBind end==========
    ============testPassAsValue start============
    5
    ============testPassAsValue end============
    ********************************
    5
    ==========testPassAsRef start==============
    5
    ==========testPassAsRef end================
    ********************************
    7
    ********************************
    ========testFunc start========
    + shared_from_this() 2
    ========testFunc end========
    *******************************************
    3
    ==========testPassAsRef start==============
    3
    ==========testPassAsRef end================
    *******************************************
    2
    ==========testPassAsRef start==============
    2
    ==========testPassAsRef end================
    ~TestClass()
    ~TestClass()
    ~TestClass()

    Process finished with exit code 0

  • 还有,易知,weak_ptr的复制不会导致shared_ptr的引用计数改变

TcpConnectionPtr的生命周期

  • TcpConnection的引用存在于

    • TcpServer中的connections_<name,TcpConnectionPtr>。shared_ptr。引用计数 = 1
    • channel 中的weak_ptr 当handleEvent时 会lock为shared_ptr(即TcpConnectionPtr) 。引用计数++ 保证不在handleEvent时析构TcpConnection
    • 还有就是在连接断开时的局部变量TcpConnectionPtr 以及 bind参数时候的拷贝TcpConnectionPtr。
  • 一个TcpConnection何时析构?

    • 断开连接后,TcpConnectionPtr = 0
    • 即整个handleEvent(handleClose)结束之后(最后一个成员函数是connetionDestroyed),TcpConnectionPtr = 0,此时TcpConnection析构(channel也就跟着析构)
  • 将一条连接断开都需要做什么操作

    • 大概来讲 就是
      • 取消对该fd的监听 :channel_->disableAll() — TcpConnection内做(因为这是TcpConnection封装的成员)
      • 执行user设置的callback:connectionCallback_(connPtr); — TcpConnection内做
      • 将TcpConnection从TcpServer中的记录移除:connections_.erase(conn->name()); — TcpServer内做(因为这是TcpServer封装的成员)
      • channel从poller中删除掉 — TcpConnection内做
  • TcpConnection析构流程 / 断开连接流程

  • poller -> channel::closeCallback -> TcpConnection::handleClose -> TcpServer::removeConnection -> TcpConnection::connectionDestoyed

  • 在进入handleClose之后,不必担心TcpConnection会在执行完成前就被销毁,因为进入之前 就已经用一个weak_ptr做了lock提升,保证了引用计数至少为1

  • 去掉一块错误注释 其余不变

  • 相关代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    //  poller ->  channel::closeCallback -> TcpConnection::handleClose -> TcpServer::removeConnection -> TcpConnection::connectionDestoyed
    // 之前注册给channel的关于关闭连接的回调操作都已经完成
    // 且TcpConnection也一直保持着没有析构。
    // 该函数结束之后,TcpConnection就会几乎马上被析构!
    void TcpConnection::handleClose()
    {
    TcpConnectionPtr connPtr(shared_from_this());

    LOG_INFO("shared_ptr cnt %ld",connPtr.use_count());
    // 3 = 1(channel::tie_.lock) + 1(TcpServer::connections_[connName] = conn;) + 1(connPtr)

    // TcpServer::removeConnection
    // **connections_.erase(TcpConnectionPtr)**
    closeCallback_(connPtr);

    LOG_INFO("shared_ptr cnt %ld tcpconnection %p point to %p",connPtr.use_count(),connPtr.get(),this);
    // 2 = 1(channel::tie_.lock) + 1(connPtr)
    }

    --------------------------------------------------------------
    // removeConnection -> removeConnectionInLoop
    void TcpServer::removeConnection(const TcpConnectionPtr &conn)
    {
    // 这里也证明了 即便形参是一个引用 即便正常调用函数传递的是个引用,可是,只要bind了,传递的就是一个值 会拷贝
    LOG_INFO("shared_ptr cnt %ld\n",conn.use_count()); // 3 没变
    loop_->runInLoop(
    std::bind(&TcpServer::removeConnectionInLoop, this, conn));
    LOG_INFO("shared_ptr cnt %ld\n",conn.use_count()); // 2
    }

    // handleRead n = 0 -> handleClose - >removeConnection -> removeConnectionInLoop -> TcpConnection::connectionDestroyed
    void TcpServer::removeConnectionInLoop(const TcpConnectionPtr &conn)
    {
    LOG_INFO("shared_ptr cnt %ld",conn.use_count()); // 4 = 3+1 因为bind是拷贝值 会导致引用计数++

    // tcpconnection use count -- : tcpserver 从记录中删除该连接
    connections_.erase(conn->name());

    LOG_INFO("after erase shared_ptr cnt %ld",conn.use_count()); // 3 = 1(channel::tie_.lock) + 1(connPtr) + 1(bind拷贝conn)

    (conn->getLoop())->runInLoop(
    std::bind(&TcpConnection::connectionDestroyed,conn.get())); // 根据以上分析 传递裸指针也可 反正在这个函数里TcpConnectionPtr的引用计数一定不是0
    // connectionDestroyed : TcpConnection析构前调用的最后一个成员函数
    }


    // 连接销毁
    void TcpConnection::connectionDestroyed()
    {
    channel_->remove(); // 把channel从poller中删除掉
    }
  • 连接断开时的相关日志如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    [INFO] 2022-10-27-22-07-43.011788 : /home/shc/Muduo/src/TcpConnection.cpp (135) <handleClose>   TcpConnection::handleClose fd = 6 state = 3

    [INFO] 2022-10-27-22-07-43.011795 : /home/shc/Muduo/src/EpollPoller.cpp (37) <updateChannel> fd = 6 , events = 0 , index = 1

    [INFO] 2022-10-27-22-07-43.011807 : /home/shc/Muduo/src/EpollPoller.cpp (90) <update> fd = 6 is to be epolled or deleted in loop 0x7ffcce544ab0 on thread 8121
    [INFO] 2022-10-27-22-07-43.011833 : /home/shc/Muduo/src/TcpConnection.cpp (146) <handleClose> shared_ptr cnt 3
    [INFO] 2022-10-27-22-07-43.011856 : /home/shc/Muduo/src/TcpConnection.cpp (148) <handleClose> shared_ptr cnt 3
    [INFO] 2022-10-27-22-07-43.011883 : testserver.cpp (40) <onConnection> connection down : from 127.0.0.1:50216 to 127.0.0.1:6667!

    [INFO] 2022-10-27-22-07-43.011944 : /home/shc/Muduo/src/TcpConnection.cpp (153) <handleClose> shared_ptr cnt 3
    [INFO] 2022-10-27-22-07-43.011981 : /home/shc/Muduo/src/TcpServer.cpp (120) <removeConnection> shared_ptr cnt 3

    [INFO] 2022-10-27-22-07-43.012043 : /home/shc/Muduo/src/EventLoop.cpp (149) <runInLoop> cb runInLoop in loop 0x7ffcce544ab0 on thread 8121
    [INFO] 2022-10-27-22-07-43.012071 : /home/shc/Muduo/src/EventLoop.cpp (155) <runInLoop> 直接调用cb on loop 0x7ffcce544ab0 in thread 8121

    [INFO] 2022-10-27-22-07-43.012101 : /home/shc/Muduo/src/TcpServer.cpp (130) <removeConnectionInLoop> shared_ptr cnt 4
    [INFO] 2022-10-27-22-07-43.012126 : /home/shc/Muduo/src/TcpServer.cpp (131) <removeConnectionInLoop> TcpServer::removeConnectionInLoop [EchoServer] - connection EchoServer-127.0.0.1:6667#1

    [INFO] 2022-10-27-22-07-43.012169 : /home/shc/Muduo/src/TcpServer.cpp (137) <removeConnectionInLoop> after erase shared_ptr cnt 3
    [INFO] 2022-10-27-22-07-43.012195 : /home/shc/Muduo/src/EventLoop.cpp (149) <runInLoop> cb runInLoop in loop 0x7ffcce544ab0 on thread 8121
    [INFO] 2022-10-27-22-07-43.012220 : /home/shc/Muduo/src/EventLoop.cpp (155) <runInLoop> 直接调用cb on loop 0x7ffcce544ab0 in thread 8121

    [INFO] 2022-10-27-22-07-43.012228 : /home/shc/Muduo/src/TcpConnection.cpp (332) <connectionDestroyed> state_ = 0
    [INFO] 2022-10-27-22-07-43.012252 : /home/shc/Muduo/src/EpollPoller.cpp (109) <removeChannel> fd = 6 , events = 0 , index = 2 in loop 0x7ffcce544ab0 on thread 8121

    [INFO] 2022-10-27-22-07-43.012274 : /home/shc/Muduo/src/EpollPoller.cpp (90) <update> fd = 6 is to be epolled or deleted in loop 0x7ffcce544ab0 on thread 8121
    [INFO] 2022-10-27-22-07-43.012311 : /home/shc/Muduo/src/TcpServer.cpp (148) <removeConnectionInLoop> after removeConnectionInLoop
    [INFO] 2022-10-27-22-07-43.012344 : /home/shc/Muduo/src/TcpServer.cpp (124) <removeConnection> shared_ptr cnt 2

    [INFO] 2022-10-27-22-07-43.012376 : /home/shc/Muduo/src/TcpConnection.cpp (160) <handleClose> shared_ptr cnt 2 tcpconnection 0x56195873ad90 point to 0x56195873ad90
    [INFO] 2022-10-27-22-07-43.012405 : /home/shc/Muduo/src/TcpConnection.cpp (45) <~TcpConnection> TcpConnection::ctor (0x56195873ad90) [EchoServer-127.0.0.1:6667#1] on socket 6 with state 0