惊群

4 分钟阅读 529 字 + 509 词
惊群:n个进程或者线程在等待某一个条件
listen之后 fork,那么其他进程都会监听这个端口
缺点:其他进程被无效唤醒了,CPU上下文频繁切换,由阻塞到被唤醒。
如何解决?
1、加锁
2、如何加锁?
3、准备一个共享内存,进程共享这块区域,内部定义一个变量(0或者1),先判断如果为0那么再将这个fd加入到epoll中,再将变量置为1。(CAS原子操作最佳,也可以上互斥锁,自旋锁)
如果是epoll_wait惊群现象呢
当多个线程使用同一个epoll实例监听同一组文件描述符时,一个文件描述符触发事件后,所有阻塞在epoll_wait的线程都可能被唤醒,但最终只有一个线程能够处理该事件。
解决这个特定的epoll惊群问题有几种有效方法:
  1. EPOLLONESHOT 标志
    • 当使用EPOLLONESHOT标志注册一个文件描述符时,一旦该描述符上的事件被触发并通知给某个线程后,epoll会自动将其从监听集合中移除,直到你重新添加它。
    • 这确保了每个事件只通知一个线程,避免了多个线程竞争同一个事件。
    c复制epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
    ev.events = EPOLLIN | EPOLLONESHOT;
  2. == EPOLLEXCLUSIVE 标志 ==(Linux 4.5+):
    • 这是专门为解决惊群问题设计的标志。
    • 使用该标志注册的描述符,当事件发生时,内核会尝试只唤醒一个等待的线程。
    c复制ev.events = EPOLLIN | EPOLLEXCLUSIVE;
    epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
  3. 实现工作池设计
    • 使用单独的线程来监听epoll事件,然后将就绪的事件分发给工作线程池中的线程处理。
    • 这种方式只有一个线程被epoll_wait唤醒,然后它负责分配工作。
  4. 每个线程使用独立的epoll实例
    • 每个线程创建和使用自己的epoll实例,并且监控不同的文件描述符集合。
    • 这完全避免了共享epoll实例带来的惊群问题。
在现代系统中,EPOLLEXCLUSIVE是解决这个问题最直接和有效的方法,它直接在内核层面解决了惊群问题,性能开销最小。