惊群笔记

惊群是什么微信

当你往一群鸽子中间扔一块食物,虽然最终只有一个鸽子抢到食物,但全部鸽子都会被惊动来争夺。这样,每扔一块食物,都会惊动全部的鸽子,即为惊群。markdown

对于操做系统而言,惊群效应是指多进程/线程同时阻塞等待获取某个资源,若是资源可用,全部等待的线程/进程会被唤起,但最终只有一个进程获取到资源的控制权,而其余进程只能从新进入休眠状态。多线程

惊群的坏处负载均衡

内核对用户进程/线程频繁地作无效的调度、上下文切换等,致使系统性能大打折扣。此外为确保只有一个进程/线程获得资源,须要对资源操做进行加锁保护,加大了系统的开销,如ngnix的实现socket

Accept惊群现象tcp

Linux解决accept惊群的方法是,引入一个排他性标志位(WQ_FLAG_EXCLUSEVE),将等待进程添加到等待队列的末尾,当有tcp链接完成,就会从半链接队列拷贝socket到链接队列,这个时候咱们就能够唤醒阻塞的accept了,此时内核唤醒队列的第一个进程后终止。性能

Epoll惊群现象优化

在讨论 epoll 的惊群效应时候,须要分为两种状况:spa

一、epoll_create fork 以前建立操作系统

流程:

1. 主进程建立listenfd, 建立epollfd2. 主进程fork多个子进程3. 每一个子进程把listenfd,加到epollfd中4. 当一个链接进来时,会触发epoll惊群,多个子进程的epoll同时会触发


accept 惊群的缘由相似,当有事件发生时,等待同一个文件描述符的全部进程(线程)都将被唤醒,解决思路和 accept 一致,共享一个epollfd, 加锁或标记解决。

二、epoll_create fork 以后建立

流程:

1. 主进程建立listendfd2. 主进程建立多个子进程3. 每一个子进程建立自已的epollfd4. 每一个子进程把listenfd加入到epollfd中5. 当一个链接进来时,会触发epoll惊群,多个子进程epoll同时会触发


每一个子进程的epoll是不一样的epoll, 虽然listenfd是同一个,但新链接过来时, accept会触发惊群,但内核不知道该发给哪一个监听进程,由于不是同一个epoll

Ngnix的解决方案

首先要知道在用户空间进程间锁实现的原理,就是能弄一个让全部进程共享的标志(因此不能是内存态的),好比mmap的内存,好比文件,而后经过这个东西来控制进程的互斥。

Nginx实现的支持两种状况,一种是支持原子操做的状况,能够直接使用 mmap,而后lock保存 mmap 的内存区域的地址,一种是不支持原子操做,使用文件锁来实现 fd共享进程间共享的文件句柄

Ngnix配置了每一个worker进程可以处理的最大链接数,当达到最大数的7/8时,该进程不会去争抢accept锁,也就不会去处理新链接,这也是一种负载均衡。

而对于争抢锁的进程,得到锁不是阻塞过程,都是马上返回,只有一个进程能获取到accept_mutex锁,这样该进程就能够调用accept获取到新链接的socketfd

这里有个优化,获取锁以后仅仅设置了一个enents标记,进程并不会立刻accept或者读取,而是将这个事件保存起来,等待释放锁以后,才会进行accept或操做这个句柄。

对于其余没有得到锁的进程,并不会立刻再去争抢锁,而是设置定时器,而后在 epoll休眠,等待下次新链接到来。

综上,当一个链接来的时候,此时每一个进程的 epoll 事件列表里面都是有该 fd 的。抢到该链接的进程先释放锁,再accept。没有抢到的进程把该 fd 从事件列表里面移除,没必要再调用 accept,形成资源浪费。

同时因为锁的控制(以及得到锁的定时器),每一个进程都能相对公平的 accept 句柄,也就是比较好的解决了子进程负载均衡。

线程池惊群

多线程中,若是使用条件变量进行生产者与消费者间的同步,当一个线程解锁并通知其余线程的时候,就会出现惊群的现象。

线程调用pthread_cond_signal时,内核会唤醒在相同条件变量上等待的一个或多个线程。若是通知了多个线程,则发生了惊群。

解决方案:

一、全部线程共用一个锁,每一个线程有自已的条件变量二、pthread_cond_signal通知时,定向通知某个线程的条件变量,不会出现惊群

参考连接:https://zhuanlan.zhihu.com/p/51251700

本文分享自微信公众号 - 机械猿(on_ourway)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索