国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Linux > 正文

Linux系統多進程多路復用喚醒沖突如何解決

2024-08-27 23:58:48
字體:
來源:轉載
供稿:網友

多路復用技術是把多個低信道組合成一個高速信道的技術,它可以有效的提高數據鏈路的利用率,從而使得一條高速的主干鏈路同時為多條低速的接入鏈路提供服務.

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.  
  8. void worker_hander(int listenfd) 
  9.     fd_set rset; 
  10.     int connfd, ret; 
  11.  
  12.     printf("worker pid#%d is waiting for connection...n", getpid()); 
  13.     for (;;) { 
  14.         FD_ZERO(&rset); 
  15.         FD_SET(listenfd,&rset); 
  16.         ret = select(listenfd+1,&rset,NULL,NULL,NULL); 
  17.         if(ret < 0) 
  18.             perror("select"); 
  19.         else if(ret > 0 && FD_ISSET(listenfd, &rset)) { 
  20.             printf("worker pid#%d 's listenfd is readablen"
  21.                     getpid()); 
  22.             connfd = accept(listenfd, NULL, 0); 
  23.             if(connfd < 0) { 
  24.                 perror("accept error"); 
  25.                 continue
  26.             } 
  27.             printf("worker pid#%d create a new connection...n"
  28.                     getpid()); 
  29.             sleep(1); 
  30.             close(connfd); 
  31.         } 
  32.     } 
  33.  
  34. static int fd_set_noblock(int fd) 
  35.     int flags; 
  36.  
  37.     flags = fcntl(fd, F_GETFL); 
  38.     if (flags == -1) 
  39.         return -1; 
  40.     flags |= O_NONBLOCK; 
  41.     flags = fcntl(fd, F_SETFL, flags); 
  42.     return flags; 
  43.  
  44. int main(int argc,char*argv[]) 
  45.     int listenfd; 
  46.     struct sockaddr_in servaddr; 
  47.     int sock_opt = 1; 
  48.  
  49.     listenfd = socket(AF_INET,SOCK_STREAM,0); 
  50.     if (listenfd < 0) { 
  51.         perror("socket"); 
  52.         exit(1); 
  53.     } 
  54.     fd_set_noblock(listenfd); 
  55.     if ((setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void *)&sock_opt, 
  56.             sizeof(sock_opt))) < 0) { 
  57.         perror("setsockopt"); 
  58.         exit(1); 
  59.     } 
  60.     bzero(&servaddr, sizeof servaddr); 
  61.     servaddr.sin_family = AF_INET; 
  62.     servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 
  63.     servaddr.sin_port = htons(1234); 
  64.     bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)); 
  65.     listen(listenfd, 10); 
  66.     //Vevb.com 
  67.     pid_t pid; 
  68.     pid = fork(); 
  69.     if (pid < 0) { 
  70.         perror("fork"); 
  71.         exit(1); 
  72.     } else if (pid == 0) 
  73.         worker_hander(listenfd); 
  74.     worker_hander(listenfd); 
  75.     return 0; 

編譯后用先運行以上的服務端,客戶端可以用 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監聽的文件描述符集合的交集為空.

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 东安县| 沁水县| 屏东县| 永清县| 玉龙| 油尖旺区| 东乡县| 石门县| 中牟县| 大埔区| 集贤县| 商城县| 微博| 萨迦县| 紫云| 桦南县| 新建县| 绥芬河市| 芜湖县| 黄浦区| 昌都县| 银川市| 乳山市| 峨边| 开封县| 湘乡市| 蓝田县| 武平县| 楚雄市| 江达县| 甘泉县| 浠水县| 汉川市| 湘潭县| 舟山市| 皋兰县| 南涧| 南阳市| 泸定县| 剑川县| 大方县|