Socket
bthread相关的基本上结束了。这次重新看一下socket
在官方文档中有说,Socket的作用是让我们可以在多线程环境下使用fd。我个人的理解就是要多线程使用需要两点,引用计数+锁。SocketID的作用类似弱引用指针,我们可以通过SocketID来获取对应socket的unique ptr。并且SocketID可以作为epoll的data,而weak ptr不可以。并且shared ptr和weak ptr不能阻止后续的引用。如果一个server要退出时,不断的有请求到来,那么引用计数可能迟迟不能归0。Socket中的SetFailed可以让Address返回空指针。这样我们可以保证最终的引用计数可以归0,从而可以顺利释放资源。
我们先看三个核心函数Create,Address以及SetFailed
SocketId由32位version + 32位slot id组成
Create的作用就是创建socket,并返回对应的socket id
在create中主要初始化一些变量。比如远端地址,用户回调函数。
最后会调用ResetFileDescriptor
如果有回调函数的话,我们会在dispatcher中注册当前的fd。也就是注册到epoll中,当事件的时候,dispatcher就会调用对应的回调函数
address用来根据socket id得到对socket的引用。并且只要返回了非空指针,那么其内容保证不会变化(也就是不会被释放)
思路还是很经典,先通过slot id拿到资源
这里的versioned_ref是version和refcnt的结合
先增加refcnt,得到vref1。然后取出vref的版本ver1。然后判断这个版本是否和id中的版本一致。如果一致我们就直接返回这个socket对应的资源
否则的话先减掉当前的refcnt,然后去检查refcnt的数量,即判断是否有其他人在使用。如果大于1的话,说明有其他人在使用,我们返回-1。
如果只有当前一个人的话,说明是版本不匹配。则可能是set failed或者被recycle了。这里我们会取到vref2的版本,然后判断奇偶性。
由于我们初始创建的版本就是偶数。所以如果当前版本为偶数,说明大版本不同,已经是不同的socket了。返回-1即可
否则的话则是奇数。说明已经是set failed的状态了。则对应了两种状态。ver1和ver2都是奇数,即都failed,或者ver1 + 1 ver2,即ver2是被set failed后获得的。那么这时候我们要尝试竞争并释放这个socket。具体的,让verf2减一的目的是减掉那个引用计数。然后通过MakeVRef来增加socket的版本,这样下一个人就可以使用这个socket了
可以看到,核心的思路就是防止我们使用的时候有并发的set failed。版本一致的情况下我们可以直接返回。版本不一致的时候,可能是跨越了大版本,也可能是set failed。当发现set failed同时ref只有0的时候,说明只有我们在访问,我们会协助帮忙回收socket。这时候回收的socket就已经和传进来的socket id无关了
然后就是SetFailed
增加version,这样后续的address就会返回null。从而让引用计数最终可以归零
然后唤醒所有等待epollout的线程。(这里猜测他的作用应该是告知其他线程socket已经failed了)
注意在最开始create的时候我们增加了一个refcnt。这里会额外减少一个,从而让refcnt变成0
这里的recycle flag的作用就是保证额外的refcnt只会被减少一次。
看一下Dereference
dereference的作用就是为socket的引用计数减一,可以看成是decrement ref cnt,或者是解除引用的含义。
首先为ref减一。然后判断引用计数。如果大于1说明还有其他人,我们就不做回收处理。
如果引用技术等于1说明我们是最后一个。
首先判断版本。这里注释说的比较清楚。set failed会将版本增加一。然后通过和address中一样的方法,用CAS去竞争,成功更改大版本的人就负责回收socket的资源。
之所以我们需要在address中也处理这个逻辑,而不是只在dereference中。是因为address需要先增加refcnt再去判断。防止在读取的时候资源被还给resource pool从而出现data race。那么address中就会出现多次refcnt从1到0的情况。我们需要在每个refcnt从0到1的场景中都尝试回收。
这次文章比较简略,核心就在于多线程情况下socket的访问以及管理。通过SetFailed的机制来保证socket最终可以被回收。
文章评论