Muduo网络库重点技术详解
可能会出现线程安全的情况
EventLoop模块
我们还需要对连接本身做管理,未来我们对连接本身做管理的时候,虽然会调用绑定的 EventLoop 模块的函数,但是调用连接管理函数的时候不一定是在绑定的 EventLoop 模块绑定的线程中执行的,那么这些操作在执行的时候,需要进行一次判断,也就是判断当前调度的线程是不是对应的连接绑定的EventLoop所绑定的线程
可能有的同学不明白调用连接管理函数的时候不一定是在绑定的 EventLoop 模块绑定的线程中执行的
主线程向工作线程的连接发送数据:
- 假设用户在主线程中调用某个连接的conn->send("hello")
- 但该连接已被分配给工作线程2
- muduo会检测到当前是主线程调用,而不是工作线程2
- 于是将send操作封装成函数,通过runInLoop投递到工作线程2执行
关闭连接:
- 主线程决定关闭某个连接conn->shutdown()
- 该连接在工作线程3中
- 关闭操作会被转发到工作线程3执行
EventLoop模块需要加锁
EventLoop模块 - eventfd
为什么需要这个 eventfd 呢? 我们说过,EventLoop 的执行流程是 监控文件描述符的事件,而这个监控我们肯定是需要阻塞的监控的,那么就会出现一种情况,我们的连接一直没有IO事件就绪,但是我们的任务队列中却已经放了很多任务了,这些对连接的管理操作就会一直得不到执行,那么未来就会出问题。 所以不仅是IO事件到来的时候需要结束这个阻塞状态,我们的任务队列中有新任务的时候也需要结束这个阻塞状态, 那么就需要一个事件的通知机制,也就是EventLoop所管理的Poller中也需要监控一个 eventfd 的读事件,每次我们向任务队列中放入任务的时候,我们需要向eventfd中写入一个数据,那么Poller 得Poll 操作就会返回,我们的 EventLoop 就能及时去执行这些任务队列的任务。
定时器模块 - 智能指针
。 当需要刷新定时任务的时候,我们只需要在 map 中找到对应的 TimerTask,然后直接在新的位置上在挂上这个任务。但是这时候存在一个问题,就是我们原来挂的这个定时任务并没有从数组中删除,同时我们也不敢直接删除,因为删除的话就需要析构了。所以我们至少不能在栈上为定时任务申请空间,而是需要在堆上申请,后续拿指针进行操作。
但是尽管如此,当我们的秒针走到这个超时任务的原定时间时,还是需要将这个位置上的任务全部执行。同时,如果是使用原生指针进行管理的话,那么我们还不能直接 clear ,我们需要一个一个遍历,然后 delete 释放堆空间。
所以现在就面临两个问题: 原位置上的定时任务会执行,并且释放空间 ; 刷新之后,新位置的指针是个野指针。
那么基于这两个问题,我们可以使用 shared_ptr 来管理堆上开辟的定时任务。
原因很简单,我们需要秒针再经过原位置的时候,直接clear,释放了旧的 shared_ptr 对象,但是由于刷新过定时任务,我们还有一个该对象的 shared_ptr 对象在后面,那么就意味着,我们不会真正释放这个堆空间,只是将计数减 1 。 只有计数为 0 的时候,才会真正释放这个对象调用析构函数,而执行定时任务。
那么就达到了刷新或者说延迟定时任务的目的。
但是又有一个问题,因为我们需要通过 map 中保存的定时任务对象的信息来构造一个新的 shared_ptr 对象加入到时间轮中,如果在 map 中也保存该对象的 shared_ptr 的话,就会占用一个计数,那么我们的定时任务就永远不会执行。 所以我们在设计 map 的时候,里面不能保存TimerTask对象指针的 shared_ptr ,而是保存他的 weak_ptr ,那么未来我们在延迟定时任务的时候,也可以直接通过 weak_ptr 来构造一个 shared_ptr , 但是我们的这个 weak_ptr 必须要从原始的shared_ptr 来,之后这样,后续使用 weak_ptr 构造的 share_ptr 对象才会和原始的 shared_ptr共享一个计数。
那么根据上面的思路,我们在数组中就使用 shared_ptr来管理任务对象,而在map中使用 shared_ptr的weak_ptr来保存定时任务的信息,方便查找以及构造新的shared_ptr 来增加原始shared_ptr的计数。
Connection模块 - shared_from_this
这个用法叫做自我引用。
为什么我们需要引进一个shared_from_this这样的接口呢?
我们说了使用shared_ptr对所有的Connection对象进行管理,这样能够防止在操作的过程中资源被释放。 但是,我们在给 _message_cb 这样的回调函数传参的时候,如何保证传给他的shared_ptr对象是和管理Conenction 的shared_ptr的对象共享计数呢?
因为如果我们直接使用 shared_ptr<Connection> p (this) ,这样创建一个只能指针对象传参的时候,他的计数是独立的,并不会和TcpServer中管理Conenction的shared_ptr共享计数,那么我们就需要一个办法能够创建出一个和Conenction 的管理的shared_ptr对象共享技术的智能指针进行传参,而shared_from_this就可以解决这样的问题。
std::enable_shared_from_this<T> 内部维护了一个 std::weak_ptr<T>。当第一个 std::shared_ptr<T> 开始管理该对象时,这个 weak_ptr 被初始化。之后,当 shared_from_this() 被调用时,它将基于这个已经存在的 weak_ptr 返回一个新的 std::shared_ptr<T>,这个新的 shared_ptr 与原有的 shared_ptr 共享对对象的所有权。
那么使用这个接口,我们就能保证在这些回调函数在执行的时候,即使其他的地方调用了_svr_close_cb把TcpServer模块中的基础计数的智能指针释放了,这份资源也还存在,至少在我们这次函数栈帧内还存在,不会出现野指针的问题。