北京SEO

Linux系统多进程多路复用唤醒冲突如何解决

2019/10/10/17:46:24  阅读:1904  来源:谷歌SEO算法  标签: 百度K站

多路复用技术是把多个低信道组合成一个高速信道的技术,它可以有效的提高数据链路的利用率,从而使得一条高速的主干链路同时为多条低速的接入链路提供服务.

Linux 对于 accept(2) 的惊群(thundering herd)问题,早已解决,目前许多人也把这种现象称为新的惊群:用多路复用模型时,不同的进程监控的文件描述符集合的交集不为空,等这个交集的某个文件IO事件触发后,内核将的多个监控了这个io且阻塞在 select(2),poll(2) 或 epoll_wait(2) 的进程唤醒,但严格来说,这种现象不叫惊群(thundering herd),而是冲突(collision).对于内核来说,唤醒所有监控这一IO事件的进程是合理的,这是因为:select/poll/epoll 不同与 accept,它们监控的文件描述符是可以被多个进程同时处理的,比如一个进程只读取这个文件句柄一小部分数据,另一进程读剩余部分,而 accept 处理的套接字是互斥的,一个套接字不能被两个进程 accept.

我注意到,对这种 select/poll/epoll 冲突的理解存在许多误区,比如有人都用如下类似的代码模拟select冲突(网上搜 select 惊群或 epoll 惊群有真相):

  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<fcntl.h>
  4. #include<stdlib.h>
  5. #include<strings.h>
  6. #include<arpa/inet.h>
  7. voidworker_hander(intlistenfd)
  8. {
  9. fd_setrset;
  10. intconnfd,ret;
  11. printf("workerpid#%diswaitingforconnection...n",getpid());
  12. for(;;){
  13. FD_ZERO(&rset);
  14. FD_SET(listenfd,&rset);
  15. ret=select(listenfd+1,&rset,NULL,NULL,NULL);
  16. if(ret<0)
  17. perror("select");
  18. elseif(ret>0&&FD_ISSET(listenfd,&rset)){
  19. printf("workerpid#%d'slistenfdisreadablen",
  20. getpid());
  21. connfd=accept(listenfd,NULL,0);
  22. if(connfd<0){
  23. perror("accepterror");
  24. continue;
  25. }
  26. printf("workerpid#%dcreateanewconnection...n",
  27. getpid());
  28. sleep(1);
  29. close(connfd);
  30. }
  31. }
  32. }
  33. staticintfd_set_noblock(intfd)
  34. {
  35. intflags;
  36. flags=fcntl(fd,F_GETFL);
  37. if(flags==-1)
  38. return-1;
  39. flags|=O_NONBLOCK;
  40. flags=fcntl(fd,F_SETFL,flags);
  41. returnflags;
  42. }
  43. intmain(intargc,char*argv[])
  44. {
  45. intlistenfd;
  46. structsockaddr_inservaddr;
  47. intsock_opt=1;
  48. listenfd=socket(AF_INET,SOCK_STREAM,0);
  49. if(listenfd<0){
  50. perror("socket");
  51. exit(1);
  52. }
  53. fd_set_noblock(listenfd);
  54. if((setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,(void*)&sock_opt,
  55. sizeof(sock_opt)))<0){
  56. perror("setsockopt");
  57. exit(1);
  58. }
  59. bzero(&servaddr,sizeofservaddr);
  60. servaddr.sin_family=AF_INET;
  61. servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
  62. servaddr.sin_port=htons(1234);
  63. bind(listenfd,(structsockaddr*)&servaddr,sizeof(servaddr));
  64. listen(listenfd,10);
  65. //phpfensi.com
  66. pid_tpid;
  67. pid=fork();
  68. if(pid<0){
  69. perror("fork");
  70. exit(1);
  71. }elseif(pid==0)
  72. worker_hander(listenfd);
  73. worker_hander(listenfd);
  74. return0;
  75. }

编译后用先运行以上的服务端,客户端可以用 netcat 模拟连接:nc 127.0.0.1 1234

以上代码是两个进程同时监控同一个文件描述符,返回的结果基本是只有一个select返回,于是试验人认为"并不是将所有工作进程全部唤醒,而只是唤醒了一部分".

这个错误的认识在于没有理解唤醒的含义,并不是要从 select(2) 返回才叫唤醒.

一个进程在等待的io事件发生之前,内核会为这个进程描述符的state字段设置 TASK_INTERRUPTIBLE 状态,此时进程描述符位于等待队列中,一旦等待的事件发生后,进程就会被唤醒,进程描述符就会被移到运行队列中,发生进程切换时,内核进程调度器会根据调度策略从运行队列选择一个进程执行.

因此,上述程序实际上唤醒了所有的两个进程,只不过先被调度的那个进程 select(2) 返回后,如果执行到accept(2) 也没有发生进程切换,把IO事件处理掉了,而等到后调度的那个进程执行时,select(2) 里面已经没有这个IO事件了,内核检测这个进程没有监控的事件发生,会把这个进程继续放到等待队列里面去,select(2) 并没有返回,这种情况的概率是非常大的,另一种概率很小的情况是:先被调度的进程执行到 accept(2) 就发生了进程切换,而在下一次运行前,调度器启动了后一个进程,这样的话,后一个进程也将会从select(2)返回.

后一种情况很不容易发生,在 accetp(2) 之前插入 usleep(3) 或 sleep(3) 就可以提高发生的概率了.

内核唤醒进程又不能让这个进程执行,再次把它移动到等待队列,造成了一定的开销浪费,nginx 是这样处理的:用一个管理进程管理多个工作进程的多路复用,工作进程在epoll_wait(2)前向管理进程申请锁,确保同一时刻,多个进程在epoll监听的文件描述符集合的交集为空.

广告内容

Linux系统多进程多路复用唤醒冲突如何解决 Linux系统多进程多路复用唤醒冲突如何解决 Linux系统多进程多路复用唤醒冲突如何解决

相关阅读

热门评论

SEO研究中心 SEO研究中心

SEO研究中心提供免费SEO公开课

总篇数170

精选文章

RMAN中catalog和nocatalog区别介绍 小技巧:为Linux下的文件分配多个权限 zimbra8.5.1安装第三方签名ssl证书的步骤 解决mysql不能远程连接数据库方法 windows服务器mysql增量备份批处理数据库 mysql中slow query log慢日志查询分析 JavaScript跨域问题总结 Linux下负载均衡软件LVS配置(VS/DR)教程 mysql中权限参数说明 MYSQL(错误1053)无法正常启动

SEO最新算法