所有学习笔记:https://github.com/Dusongg/StudyNotes
文章目录
- epoll数据结构的选择?
- 以tcp为例,网络io的可读可写如何判断?
- epoll如何做到线程安全?
- LT和ET如何实现?
- tcp状态和io的读写有哪些关系?
- epoll_wait等待io唤醒过程(以建立tcp建立链接为例)
epoll数据结构的选择?
- rbtree/hash/btree/b+tree?
- hash内存浪费
epoll内核为什么不选择哈希表而是选择红黑树?
在Linux的
epoll
中,为了实现高效的事件管理,epoll
内核模块采用了红黑树作为事件的存储结构,而不是哈希表。这是因为红黑树在查找、插入和删除等操作上具有较好的性能表现,特别是对于大量事件的管理。红黑树是一种自平衡的二叉查找树,具有以下特点:
- 在最坏情况下,插入、删除和查找操作的时间复杂度为O(log n),保证了较好的性能。
- 红黑树的结构相对稳定,对于动态添加和删除节点的场景,红黑树能够保持平衡,减少了不必要的树旋转操作。
- 红黑树的高度较低,使得在进行查找操作时能够快速定位到目标节点。
相比之下,哈希表虽然在平均情况下具有O(1)的插入、删除和查找操作,但在处理大规模数据时,哈希表的冲突处理、扩容和缩小等操作会引入额外的开销,而且哈希表的遍历不如红黑树高效,因此在这种场景下,红黑树更适合作为
epoll
的事件管理数据结构
- btree/b+tree更适合磁盘查找
所以最终
io集合用红黑树管理;就绪集合用队列管理;队列和红黑树公用一个节点
以tcp为例,网络io的可读可写如何判断?
-
对于tcp而言,哪些事件是的io变为就绪:
-
三次握手完成 -> listenfd可读
-
当recvbuffer接收到数据 -> clientfd可读
-
当sendbuffer有空间 -> clientfd可写
-
当接收到FIN包 -> clientfd可读
-
epoll如何做到线程安全?
一把锁锁rbtree -> mutex:得不到锁,休眠
一把锁锁queue -> spinlock :得不到锁,一直等待
自旋锁和互斥锁都是用于多线程编程中实现同步的机制,但它们有一些区别:
- 等待方式:
- 自旋锁:线程在尝试获取锁时,如果锁已经被其他线程持有,该线程会一直处于忙等(自旋)状态,直到锁被释放。
- 互斥锁:线程在尝试获取锁时,如果锁已经被其他线程持有,该线程会被阻塞,直到锁被释放。
- 实现机制:
- 自旋锁:通常使用原子操作来实现,因此适用于轻量级的同步操作。
- 互斥锁:通常使用操作系统提供的系统调用(如
pthread_mutex_lock
)来实现,因此会涉及到用户态和内核态的切换,性能开销较大。- 适用场景:
- 自旋锁:适用于锁被持有的时间很短的情况,避免了线程切换的开销。
- 互斥锁:适用于锁被持有的时间较长的情况,可以让等待的线程进入睡眠状态,不会占用CPU资源。
- 实现复杂度:
- 自旋锁:实现相对简单,主要依赖于原子操作。
- 互斥锁:实现相对复杂,需要考虑线程阻塞、唤醒等操作。
LT和ET如何实现?
tcp内部有一个循环,内核recvbuffer。
-
LT判断recvbuffer有数据就回调,往就绪队列里增加节点,节点状态为就绪
-
ET判断有数据写入recvbuffer才回调,往队列里增加节点,节点状态为就绪
tcp状态和io的读写有哪些关系?
tcp调用回调函数,将事件添加到就绪队列
epoll中实现回调函数,当回调函数被tcp调用时,pthread_cond_signal
唤醒epoll_wait
中的条件变量
epoll_wait等待io唤醒过程(以建立tcp建立链接为例)
ll_wait等待io唤醒过程(以建立tcp建立链接为例)
[外链图片转存中…(img-YvWU16DH-1710004476688)]