前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入剖析Chrome Base库中的异步I/O利器:揭秘WatchFileDescriptor

深入剖析Chrome Base库中的异步I/O利器:揭秘WatchFileDescriptor

作者头像
陆业聪
发布2024-07-23 18:26:13
710
发布2024-07-23 18:26:13
举报
文章被收录于专栏:大前端修炼手册

一、WatchFileDescriptor简介

在现代计算机系统中,I/O操作是非常重要的一部分,它们通常包括读取或写入文件、网络通信等。然而,由于I/O操作通常涉及到硬件设备,其速度远远低于CPU和内存的处理速度,因此,如何高效地处理I/O操作,是一个重要的问题。

在Chrome浏览器的开源项目中,有一个名为base::MessageLoopForIO::current()->WatchFileDescriptor的API,它提供了一种异步的、基于事件驱动的方式来处理I/O操作,使得我们可以在不阻塞主线程的情况下处理大量的I/O操作。

二、WatchFileDescriptor的原理和用法

2.1 IO事件分发过程

base::MessageLoopForIO是一个事件循环,用于处理I/O相关的任务和事件。事件循环是事件驱动编程的核心,它会循环等待和处理事件。在这个过程中,base::MessageLoopForIO会将文件描述符和相应的事件处理器关联起来,当文件描述符上发生事件时,对应的处理器就会被调用。

base::MessageLoopForIO将文件描述符和相应的事件处理器关联起来的过程主要通过WatchFileDescriptor函数实现。在这个过程中,base::MessageLoopForIO使用了事件分发器(event dispatcher)和文件描述符监视器(file descriptor watcher)来实现文件描述符和事件处理器的关联。

以下是IO事件分发的过程:

  1. 当调用WatchFileDescriptor时,需要传入文件描述符、监视模式、文件描述符监视器(FileDescriptorWatcher)和事件处理器委托(FileDescriptorWatcher::Delegate)。
  2. WatchFileDescriptor首先会检查当前的消息循环是否支持IO事件。如果支持,它会获取当前线程的事件分发器(event dispatcher),如base::MessagePumpForIO(在Windows上)或base::MessagePumpLibevent(在其他平台上)。
  3. 事件分发器负责将文件描述符、监视模式、文件描述符监视器和事件处理器委托关联起来。具体实现取决于底层的事件驱动库,如Windows上的IOCP(I/O完成端口)或其他平台上的libevent。事件分发器会将文件描述符添加到事件驱动库,并设置相应的回调函数和上下文数据。
  4. 当文件描述符上发生事件时,事件驱动库会调用之前设置的回调函数,并传递相应的上下文数据。这个回调函数通常是事件分发器的一个私有方法,如base::MessagePumpLibevent::OnLibeventNotification
  5. 在回调函数中,事件分发器会根据上下文数据找到对应的文件描述符监视器和事件处理器委托,然后调用委托的OnFileCanReadWithoutBlockingOnFileCanWriteWithoutBlocking方法来处理事件。

通过以上步骤,base::MessageLoopForIO将文件描述符和相应的事件处理器关联起来,并实现了异步的、基于事件驱动的IO事件处理。这种机制使得我们可以在不阻塞主线程的情况下处理大量的IO操作,提高了程序的性能和响应速度。

2.2 WatchFileDescriptor用法

使用base::MessageLoopForIO::current()->WatchFileDescriptor,我们可以指定要监视的文件描述符、是否持续监视、监视模式(读、写或读写)、文件描述符监视器以及事件处理器委托。当文件描述符上发生事件时,会调用事件处理器委托的OnFileCanReadWithoutBlockingOnFileCanWriteWithoutBlocking方法。

base::MessageLoopForIO::current()->WatchFileDescriptor是Chrome浏览器开源项目中的一个API,它是基于事件驱动模型实现的,用于监视文件描述符的IO事件,如读、写或异常事件。

WatchFileDescriptor函数的原型如下:

代码语言:javascript
复制
bool WatchFileDescriptor(int fd,
                         bool persistent,
                         WatchMode mode,
                         FileDescriptorWatcher* controller,
                         FileDescriptorWatcher::Delegate* delegate);

参数解释如下:

  • fd: 要监视的文件描述符。
  • persistent: 是否持续监视。如果设置为true,那么在处理完一个事件后,文件描述符仍然会被监视;如果设置为false,那么在处理完一个事件后,监视会被自动取消。
  • mode: 监视模式,可以是WATCH_READWATCH_WRITEWATCH_READ_WRITE,分别代表监视读事件、写事件或读写事件。
  • controller: 一个FileDescriptorWatcher对象,用于控制监视操作。可以通过它来取消监视。
  • delegate: 当文件描述符上发生事件时,会调用这个委托的OnFileCanReadWithoutBlockingOnFileCanWriteWithoutBlocking方法。

使用示例:

代码语言:javascript
复制
class MyDelegate : public base::MessageLoopForIO::Watcher {
 public:
  void OnFileCanReadWithoutBlocking(int fd) override {
    // 处理读事件
  }
  void OnFileCanWriteWithoutBlocking(int fd) override {
    // 处理写事件
  }
};

int fd = ...;  // 要监视的文件描述符
MyDelegate delegate;
base::MessageLoopForIO::FileDescriptorWatcher watcher;

base::MessageLoopForIO::current()->WatchFileDescriptor(
    fd, true, base::MessageLoopForIO::WATCH_READ, &watcher, &delegate);

在这个示例中,我们创建了一个MyDelegate对象,并将其与文件描述符关联起来,用于处理读事件。当文件描述符上发生读事件时,MyDelegate::OnFileCanReadWithoutBlocking方法就会被调用。

base::MessageLoopForIO::current()->WatchFileDescriptor提供了一种异步的、基于事件驱动的方式来处理IO事件,使得我们可以在不阻塞主线程的情况下处理大量的IO操作。

三、Linux上的I/O多路复用技术

3.1 Chrome中的libevent

Chrome浏览器的底层网络库NetLog使用了libevent作为其I/O多路复用的机制。libevent是一个轻量级的、高性能的事件通知库,它封装了底层的I/O多路复用系统调用,并提供了统一的API。在Linux平台上,libevent首选使用epoll作为I/O多路复用的实现,如果不支持epoll,则会回退到pollselect

epoll是Linux特有的I/O多路复用技术,相比selectpoll,它具有更高的性能和可扩展性。epoll使用事件驱动的方式来通知应用程序I/O事件,避免了在每次调用时遍历所有文件描述符的开销。应用程序可以通过epoll_createepoll_ctlepoll_wait等系统调用来使用epoll

使用libevent的优点在于,它提供了一种异步的、基于事件驱动的方式来处理I/O事件,使得我们可以在不阻塞主线程的情况下处理大量的I/O操作。这对于Chrome这样的大型项目来说非常重要,因为它需要处理大量的网络请求,如HTTP请求、WebSocket连接等。

此外,libevent还提供了其他的功能,如定时器、信号处理、缓冲I/O等,这些功能都可以帮助我们更好地处理I/O操作。例如,我们可以使用libevent的缓冲I/O功能来简化数据的读写操作,而不需要直接操作底层的read和write系统调用。

3.2 Linux平台上/O多路复用的系统调用接口

在Linux平台上,这种异步的、事件驱动的I/O处理方式的实现主要基于I/O多路复用技术。I/O多路复用允许一个线程同时监视多个文件描述符(如套接字)上的I/O事件,从而提高程序的并发性能。Linux提供了多种I/O多路复用的系统调用,如selectpollepoll等。

selectpollepoll都是Linux系统提供的I/O多路复用机制,它们都能同时监视多个文件描述符上的I/O事件。然而,它们在API设计、性能、触发方式和最大文件描述符限制等方面有所不同。下面我们通过示例代码来详细介绍它们的区别:

3.2.1 select 示例代码
代码语言:javascript
复制
fd_set readfds;
struct timeval timeout;
int ret;

/* 清空文件描述符集 */
FD_ZERO(&readfds);

/* 添加文件描述符到集合 */
FD_SET(fd, &readfds);

/* 设置超时时间 */
timeout.tv_sec = 5;
timeout.tv_usec = 0;

/* 监视文件描述符集合 */
ret = select(fd + 1, &readfds, NULL, NULL, &timeout);

/* 检查返回值 */
if (ret == -1) {
    perror("select");
} else if (ret) {
    printf("Data is available now.\n");
} else {
    printf("No data within five seconds.\n");
}
3.2.2 poll 示例代码
代码语言:javascript
复制
struct pollfd fds[1];
int ret;

/* 设置要监视的文件描述符 */
fds[0].fd = fd;
fds[0].events = POLLIN;

/* 监视文件描述符 */
ret = poll(fds, 1, 5000);

/* 检查返回值 */
if (ret == -1) {
    perror("poll");
} else if (ret) {
    printf("Data is available now.\n");
} else {
    printf("No data within five seconds.\n");
}
3.2.3 epoll 示例代码
代码语言:javascript
复制
int epollfd, nfds;
struct epoll_event ev, events[MAX_EVENTS];

/* 创建 epoll 实例 */
epollfd = epoll_create1(0);
if (epollfd == -1) {
    perror("epoll_create1");
    exit(EXIT_FAILURE);
}

/* 添加文件描述符到 epoll 实例 */
ev.events = EPOLLIN;
ev.data.fd = fd;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
    perror("epoll_ctl");
    exit(EXIT_FAILURE);
}

/* 等待文件描述符上的事件 */
nfds = epoll_wait(epollfd, events, MAX_EVENTS, 5000);

/* 处理事件 */
for (int n = 0; n < nfds; ++n) {
    if (events[n].data.fd == fd) {
        printf("Data is available now.\n");
    }
}
3.2.4 主要区别
  1. API设计selectpoll的API设计相对简单,它们每次调用都需要传递全部的文件描述符,而且在返回后需要遍历所有的文件描述符来检查哪些发生了事件。epoll的API设计更为复杂,但是它只需要在添加或删除文件描述符时调用API,并且在返回后可以直接得知哪些文件描述符发生了事件。
  2. 性能:当文件描述符数量较多时,selectpoll的性能会下降,因为它们需要遍历所有的文件描述符。而epoll则没有这个问题,它使用事件驱动的方式,只处理发生了事件的文件描述符,因此具有更高的性能和可扩展性。
  3. 触发方式selectpoll只支持水平触发(Level Triggered,LT),即只要文件描述符上有未处理的I/O事件,就会一直触发。而epoll则同时支持水平触发和边缘触发(Edge Triggered,ET)。在边缘触发模式下,只有文件描述符的I/O状态发生变化时,才会触发事件,这可以减少事件通知的次数,提高效率。
  4. 最大文件描述符限制select的最大文件描述符数量受限于FD_SETSIZE(通常是1024),而pollepoll没有这个限制。

总的来说,selectpollepoll各有优缺点,适用于不同的场景。在文件描述符数量较少,或者需要跨平台兼容性的情况下,可以使用selectpoll。而在文件描述符数量较多,或者需要更高性能和灵活性的情况下,推荐使用epoll

四、再看WatchFileDescriptor中异步I/O机制的作用

回到Chrome浏览器的开源项目,base::MessageLoopForIO::current()->WatchFileDescriptor这个API的设计使得我们可以方便地将文件描述符和相应的事件处理器关联起来,从而实现异步的、基于事件驱动的I/O操作。这种机制在实际应用中非常有用,因为它可以在不阻塞主线程的情况下处理大量的I/O操作,提高了程序的性能和响应速度。

例如,在网络编程中,我们可能需要处理大量的客户端连接。传统的同步I/O方式会导致服务器在处理一个客户端请求时,其他客户端的请求被阻塞。而使用base::MessageLoopForIO::current()->WatchFileDescriptor这样的异步I/O机制,我们可以在不阻塞主线程的情况下处理多个客户端的请求,从而提高服务器的并发性能。

此外,这种异步I/O机制还可以用于处理其他类型的I/O操作,如文件读写、数据库访问等,使得我们可以在不影响主线程的响应速度的前提下,执行耗时的I/O操作。

五、总结

总之,base::MessageLoopForIO::current()->WatchFileDescriptor这个API为我们提供了一种高效、灵活的异步I/O处理方式,它基于事件驱动模型和I/O多路复用技术,使得我们可以在不阻塞主线程的情况下处理大量的I/O操作。这种机制在实际应用中可以提高程序的性能和响应速度。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-04-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 陆业聪 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 二、WatchFileDescriptor的原理和用法
    • 2.1 IO事件分发过程
      • 2.2 WatchFileDescriptor用法
      • 三、Linux上的I/O多路复用技术
        • 3.1 Chrome中的libevent
          • 3.2 Linux平台上/O多路复用的系统调用接口
            • 3.2.1 select 示例代码
            • 3.2.2 poll 示例代码
            • 3.2.3 epoll 示例代码
            • 3.2.4 主要区别
        • 四、再看WatchFileDescriptor中异步I/O机制的作用
        • 五、总结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档