频繁的I/O操作会引起频繁的系统调用,这会很慢,于是引入缓冲区。对于一个流(文件、socket或pipe),以缓冲区为单位进行操作,举个例子:
一个管道,A写入,B读出,一开始内核缓冲区为空,B阻塞,A开始写入,内核缓冲区状态由空变为非空,这时内核产生一个事件告诉B该醒了。但这个事件并没有让B去读数据,似乎只是起到一个警示,不过内核许诺不会把写入管道中的数据丢掉,A写入的数据会全部保存在缓冲区中,但如果内核缓冲区满了,B还未读数据,这时就会产生I/O事件,告诉进程A不要再写入了,进入阻塞吧。后来B终于开始读数据了,当内核缓冲区空了出来,这时内核就会告诉A,内核缓冲区有空位了,你可以醒了,这也是一个事件。同理,当A不再写入数据,而B将缓冲区读空了,这时内核会让B阻塞。
这个例子是阻塞I/O,在这种模式下,一个线程只能处理一个流的I/O事件,如果要同时处理多个流,需要多进程或多线程,但它们的效率都不高。如果想要用一个线程去处理多个流,只有去轮询多个流,这显然是低效率的。但是如果我们能在轮询之前能判断在这些流中是否产生了I/O事件就好了,这样当得知没有I/O事件,就不去轮询,当得知有I/O事件,再去轮询。这就是select。不过我们从select那里只能知道有I/O事件发生了,却并不知道是那几个流,所以需要轮询全部,还是不够高效。
于是epoll诞生了。epoll会把哪个流发生了怎样的I/O事件通知我们,这样复杂度由O(n)降到O(1),perfect。一个epoll模式的代码大概的样子是:
while true { active_stream[] = epoll_wait(epollfd); // 没有I/O事件时阻塞在此, // 当有I/O事件时,返回具体的哪个流产生了什么样的事件 for i in active_stream[] { read or write till; } }
时间: 2024-10-14 05:46:01