本文主要是介绍linux惊群效应,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Linux惊群效应
- 1.什么是惊群效应
- 2.惊群效应有什么影响
- 3.常见惊群情况
- 1. accept惊群
- 2. epoll惊群
- 1 是在fork之前创建epollfd,所有进程共用一个epoll;
- 2 是在fork之后创建epollfd,每个进程独用一个epoll
- 3. nginx惊群
- 4. 线程池惊群
- 3.accept惊群效应验证
- 4.epoll惊群效应验证
1.什么是惊群效应
定义:多进程(多线程)在同时阻塞等待同一个事件的时候(休眠状态),如果等待的这个事件发生,那么他就会唤醒等待的所有进程(或者线程),但是最终却只可能有一个进程(线程)获得这个时间的“控制权”,对该事件进行处理,而其他进程(线程)获取“控制权”失败,只能重新进入休眠状态,这种现象和性能浪费就叫做惊群
简单地说:就是扔一块食物,所有鸽子来抢,但最终只一个鸽子抢到了食物。
语义分析:食物只有一块,最终只有一个鸽子抢到,但是惊动了所有鸽子,每个鸽子都跑过来,消耗了每个鸽子的能量。(这个很符合达尔文的进化论,物种之间的竞争,适者生存。)
2.惊群效应有什么影响
- 惊醒所有进程/线程,导致n-1个进程/线程做了无效的调度,上下文切换,cpu瞬时增高
- 多个进程/线程争抢资源,所以涉及到同步问题,需对资源进行加锁保护,加解锁加大系统CPU开销
3.常见惊群情况
惊群的几种情况
在高并发(多线程/多进程/多连接)中,会产生惊群的情况有:
1. accept惊群
在内核2.6及之后,解决了惊群,在内核中增加了一个互斥等待变量。一个互斥等待的行为与睡眠基本类似,主要的不同点在于:
1)当一个等待队列入口有 WQ_FLAG_EXCLUSEVE 标志置位, 它被添加到等待队列的尾部. 没有这个标志的入口项, 相反, 添加到开始.
2)当 wake_up 被在一个等待队列上调用时, 它在唤醒第一个有 WQ_FLAG_EXCLUSIVE 标志的进程后停止。
对于互斥等待的行为,比如如对一个listen后的socket描述符,多线程阻塞accept时,系统内核只会唤醒所有正在等待此时间的队列 的第一个,队列中的其他人则继续等待下一次事件的发生,这样就避免的多个线程同时监听同一个socket描述符时的惊群问题。
2. epoll惊群
1 是在fork之前创建epollfd,所有进程共用一个epoll;
-
主进程创建listenfd, 创建epollfd
-
主进程fork多个子进程
-
每个子进程把listenfd,加到epollfd中
当一个连接进来时,会触发epoll惊群,多个子进程的epoll同时会触发
linux内核通过和accept加锁方式解决
2 是在fork之后创建epollfd,每个进程独用一个epoll
-
主进程创建listendfd
-
主进程创建多个子进程
-
每个子进程创建自已的epollfd
-
每个子进程把listenfd加入到epollfd中
-
当一个连接进来时,会触发epoll惊群,多个子进程epoll同时会触发
分析:
因为每个子进程的epoll是不同的epoll, 虽然listenfd是同一个,但新连接过来时, accept会触发惊群,但内核不知道该发给哪个监听进程,因为不是同一个epoll。所以这种惊群内核并没有处理。惊群还是会出现。
3. nginx惊群
4. 线程池惊群
3.accept惊群效应验证
demo验证
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>#include "epoll.hpp"#define PROCESS_NUMBER 10void test_accept()
{int fd = socket(PF_INET,SOCK_STREAM,0);struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_addr.s_addr = htonl(INADDR_ANY);addr.sin_port = htons(8080);bind(fd, (struct sockaddr*)&addr, sizeof(addr));listen(fd,1024);int pid = -1;int connfd = 0;char sendbuf[1024] = {0};for(int i = 0 ; i < PROCESS_NUMBER; i++){pid = fork();if(pid == 0){while(true){connfd = accept(fd,(struct sockaddr*)NULL,NULL);if(connfd < 0){printf("process %d accept a connection failed: %s\n", getpid(), strerror(errno));close(connfd);}else{snprintf(sendbuf,sizeof(sendbuf),"accept event, pid = %d", getpid());send(connfd,sendbuf,strlen(sendbuf)+1, 0);printf("process %d accept success\n", getpid());close(connfd);}}}}wait(0);
}int main()
{test_accept();return 0;
}
结果
只有一个进程被触发
其实在linux2.6版本以后,linux内核已经解决了accept()函数的“惊群”现象,大概的处理方式就是,当内核接收到一个客户连接后,只会唤醒等待队列上的第一个进程(线程),所以如果服务器采用accept阻塞调用方式,在最新的linux系统中已经没有“惊群效应”了
4.epoll惊群效应验证
编写demo验证
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>#define PROCESS_NUM 3
#define MAXEVENTS 64int create_bind_socket(int port)
{int sock = socket(AF_INET,SOCK_STREAM, 0);struct sockaddr_in serveraddr;serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(port);serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);bind(sock,(struct sockaddr*)&serveraddr,sizeof(serveraddr));return sock;
}int make_nonblocking(int fd)
{int val = fcntl(fd, F_GETFL);val |= O_NONBLOCK;if(fcntl(fd,F_SETFL,val) < 0){perror("fcntl set error");return -1;}return 0;
}void epoll_demo()
{struct epoll_event event;struct epoll_event* events;int sock_fd = -1;int epoll_fd = -1;if((sock_fd = create_bind_socket(8080)) < 0){perror("socket and bind fail");exit(1);}listen(sock_fd,1024);epoll_fd = epoll_create(MAXEVENTS);event.data.fd = sock_fd;event.events = EPOLLIN;epoll_ctl(epoll_fd,EPOLL_CTL_ADD,sock_fd,&event);events = (epoll_event*)calloc(MAXEVENTS, sizeof(event));for(int i = 0 ; i < PROCESS_NUM; ++i){int pid = fork();if(pid == 0){while(true){int num = epoll_wait(epoll_fd, events, MAXEVENTS, -1);printf("process %d return epoll_wait\n", getpid());sleep(2);for(int j = 0; j < num; ++j){if((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) || (!(events[i].events & EPOLLIN))){printf("process %d epoll error\n", getpid());close(events[i].data.fd);continue;}else if(sock_fd == events[i].data.fd){struct sockaddr in_addr;socklen_t in_len = sizeof(in_addr);if(accept(sock_fd, &in_addr, &in_len) < 0){printf("process %d accept faild\n", getpid());}else{printf("process %d accept successful\n", getpid());}}}}}}wait(0);free(events);close(sock_fd);
}
结果
telnet 发现3个子进程都被触发了响应
accpet linux内核修改了只能被一个进程调用成功,但epoll不一样,他监听的文件描述符,除了可能后续被accept调用外,还有可能是其他网络IO事件的,而其他IO事件是否只能由一个进程处理,是不一定的,内核不能保证这一点,这是一个由用户决定的事情,例如可能一个文件会由多个进程来读写。所以,对epoll的惊群,内核则不予处理
这篇关于linux惊群效应的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!