Linux驚群效應詳解(最詳細的了吧)

https://blog.csdn.net/lyztyycode/article/details/78648798?locationNum=6&fps=1

linux驚群效應

詳細的介紹什么是驚群,驚群在線程和進程中的具體表現,驚群的系統消耗和驚群的處理方法。

1、驚群效應是什么?

驚群效應也有人叫做雷鳴群體效應,不過叫什么,簡言之,驚群現象就是多進程(多線程)在同時阻塞等待同一個事件的時候(休眠狀態),如果等待的這個事件發生,那么他就會喚醒等待的所有進程(或者線程),但是最終卻只可能有一個進程(線程)獲得這個時間的“控制權”,對該事件進行處理,而其他進程(線程)獲取“控制權”失敗,只能重新進入休眠狀態,這種現象和性能浪費就叫做驚群。
為了更好的理解何為驚群,舉一個很簡單的例子,當你往一群鴿子中間扔一粒谷子,所有的各自都被驚動前來搶奪這粒食物,但是最終注定只可能有一個鴿子滿意的搶到食物,沒有搶到的鴿子只好回去繼續睡覺,等待下一粒谷子的到來。這里鴿子表示進程(線程),那粒谷子就是等待處理的事件。

看一下:WIKI的雷鳴群體效應的解釋

2.驚群效應到底消耗了什么?

我想你應該也會有跟我一樣的問題,那就是驚群效應到底消耗了什么?
? ???(1)、系統對用戶進程/線程頻繁地做無效的調度,上下文切換系統性能大打折扣。
(2)、為了確保只有一個線程得到資源,用戶必須對資源操作進行加鎖保護,進一步加大了系統開銷。
是不是還是覺得不夠深入,概念化?看下面:
? ? ???? *1、上下文切換(context? switch)過高會導致cpu像個搬運工,頻繁地在寄存器和運行隊列之間奔波,更多的時間花在了進程(線程)切換,而不是在真正工作的進程(線程)上面。直接的消耗包括cpu寄存器要保存和加載(例如程序計數器)、系統調度器的代碼需要執行。間接的消耗在于多核cache之間的共享數據。
看一下:wiki上下文切換
*2、通過鎖機制解決驚群效應是一種方法,在任意時刻只讓一個進程(線程)處理等待的事件。但是鎖機制也會造成cpu等資源的消耗和性能損耗。目前一些常見的服務器軟件有的是通過鎖機制解決的,比如nginx(它的鎖機制是默認開啟的,可以關閉);還有些認為驚群對系統性能影響不大,沒有去處理,比如lighttpd。

3.驚群效應的廬山真面目。

讓我們從進程和線程兩個方面來揭開驚群效應的廬山真面目:

*1)accept()驚群:

首先讓我們先來考慮一個場景:
? ? ? ??主進程創建了socket、bind、listen之后,fork()出來多個進程,每個子進程都開始循環處理(accept)這個listen_fd。每個進程都阻塞在accept上,當一個新的連接到來時候,所有的進程都會被喚醒,但是其中只有一個進程會接受成功,其余皆失敗,重新休眠。
那么這個問題真的存在嗎?
? ? ? ?歷史上,Linux的accpet確實存在驚群問題,但現在的內核都解決該問題了。即,當多個進程/線程都阻塞在對同一個socket的接受調用上時,當有一個新的連接到來,內核只會喚醒一個進程,其他進程保持休眠,壓根就不會被喚醒。
? ? ?? 不妨寫個程序測試一下,眼見為實:
fork_thunder_herd.c:
[cpp]?view plaincopy
  1. #include<stdio.h>??
  2. #include<stdlib.h>??
  3. #include<sys/types.h>??
  4. #include<sys/socket.h>??
  5. #include<sys/wait.h>??
  6. #include<string.h>??
  7. #include<netinet/in.h>??
  8. #include<unistd.h>??
  9. ??
  10. #define?PROCESS_NUM?10??
  11. int?main()??
  12. {??
  13. ????int?fd?=?socket(PF_INET,?SOCK_STREAM,?0);??
  14. ????int?connfd;??
  15. ????int?pid;??
  16. ??
  17. ????char?sendbuff[1024];??
  18. ????struct?sockaddr_in?serveraddr;??
  19. ????serveraddr.sin_family?=?AF_INET;??
  20. ????serveraddr.sin_addr.s_addr?=?htonl(INADDR_ANY);??
  21. ????serveraddr.sin_port?=?htons(1234);??
  22. ????bind(fd,?(struct?sockaddr?*)&serveraddr,?sizeof(serveraddr));??
  23. ????listen(fd,?1024);??
  24. ????int?i;??
  25. ????for(i?=?0;?i?<?PROCESS_NUM;?++i){??
  26. ????????pid?=?fork();??
  27. ????????if(pid?==?0){??
  28. ????????????while(1){??
  29. ????????????????connfd?=?accept(fd,?(struct?sockaddr?*)NULL,?NULL);??
  30. ????????????????snprintf(sendbuff,?sizeof(sendbuff),?"接收到accept事件的進程PID?=?%d\n",?getpid());??
  31. ??
  32. ????????????????send(connfd,?sendbuff,?strlen(sendbuff)+1,?0);??
  33. ????????????????printf("process?%d?accept?success\n",?getpid());??
  34. ????????????????close(connfd);??
  35. ????????????}??
  36. ????????}??
  37. ????}??
  38. ????//int?status;??
  39. ????wait(0);??
  40. ????return?0;??
  41. }??
這個程序模擬上面的場景,當我們用telnet連接該服務器程序時,會看到只返回一個進程pid,即只有一個進程被喚醒。
我們用strace -f來追蹤fork子進程的執行:
編譯:cc fork_thunder_herd.c -o server
? ? ? ? ???一個終端執行strace -f? ./server??你會看到如下結果(只截取部分可以說明問題的截圖,減小篇幅):
這里我們首先看到系統創建了十個進程。下面這張圖你會看出十個進程阻塞在accept這個系統調用上面:
接下來在另一個終端執行telnet 127.0.0.1 1234:

很明顯當telnet連接的時候只有一個進程accept成功,你會不會和我有同樣的疑問,就是會不會內核中喚醒了所有的進程只是沒有獲取到資源失敗了,就好像驚群被“隱藏”?

這個問題很好證明,我們修改一下代碼:

[cpp]?view plaincopy
  1. connfd?=?accept(fd,?(struct?sockaddr?*)NULL,?NULL);??
  2. if(connfd?==?0){??
  3. ??
  4. ????snprintf(sendbuff,?sizeof(sendbuff),?"接收到accept事件的進程PID?=?%d\n",?getpid());??
  5. ??
  6. ????send(connfd,?sendbuff,?strlen(sendbuff)+1,?0);??
  7. ????printf("process?%d?accept?success\n",?getpid());??
  8. ????close(connfd);??
  9. }else{??
  10. ????printf("process?%d?accept?a?connection?failed:?%s\n",?getpid(),?strerror(errno));??
  11. ????close(connfd);??
  12. }??

沒錯,就是增加了一個accept失敗的返回信息,按照上面的步驟運行,這里我就不截圖了,我只告訴你運行結果與上面的運行結果無異,增加的失敗信息并沒有輸出,也就說明了這里并沒有發生驚群,所以注意阻塞和驚群的喚醒的區別。

Google了一下:其實在linux2.6版本以后,linux內核已經解決了accept()函數的“驚群”現象,大概的處理方式就是,當內核接收到一個客戶連接后,只會喚醒等待隊列上的第一個進程(線程),所以如果服務器采用accept阻塞調用方式,在最新的linux系統中已經沒有“驚群效應”了

accept函數的驚群解決了,下面來讓我們看看存在驚群現象的另一種情況:epoll驚群

*2)epoll驚群:

概述:
如果多個進程/線程阻塞在監聽同一個監聽socket?fd的epoll_wait上,當有一個新的連接到來時,所有的進程都會被喚醒。
同樣讓我們假設一個場景:
主進程創建socket,bind,listen后,將該socket加入到epoll中,然后fork出多個子進程,每個進程都阻塞在epoll_wait上,如果有事件到來,則判斷該事件是否是該socket上的事件如果是,說明有新的連接到來了,則進行接受操作。為了簡化處理,忽略后續的讀寫以及對接受返回的新的套接字的處理,直接斷開連接。
那么,當新的連接到來時,是否每個阻塞在epoll_wait上的進程都會被喚醒呢?
很多博客中提到,測試表明雖然epoll_wait不會像接受那樣只喚醒一個進程/線程,但也不會把所有的進程/線程都喚醒。
這究竟是問什么呢?
看一下:多進程epoll和“驚群”

我們還是眼見為實,一步步解決上面的疑問:

代碼實例:epoll_thunder_herd.c:

[cpp]?view plaincopy
  1. #include<stdio.h>??
  2. #include<sys/types.h>??
  3. #include<sys/socket.h>??
  4. #include<unistd.h>??
  5. #include<sys/epoll.h>??
  6. #include<netdb.h>??
  7. #include<stdlib.h>??
  8. #include<fcntl.h>??
  9. #include<sys/wait.h>??
  10. #include<errno.h>??
  11. #define?PROCESS_NUM?10??
  12. #define?MAXEVENTS?64??
  13. //socket創建和綁定??
  14. int?sock_creat_bind(char?*?port){??
  15. ????int?sock_fd?=?socket(AF_INET,?SOCK_STREAM,?0);??
  16. ????struct?sockaddr_in?serveraddr;??
  17. ????serveraddr.sin_family?=?AF_INET;??
  18. ????serveraddr.sin_port?=?htons(atoi(port));??
  19. ????serveraddr.sin_addr.s_addr?=?htonl(INADDR_ANY);??
  20. ??
  21. ????bind(sock_fd,?(struct?sockaddr?*)&serveraddr,?sizeof(serveraddr));??
  22. ????return?sock_fd;??
  23. }??
  24. //利用fcntl設置文件或者函數調用的狀態標志??
  25. int?make_nonblocking(int?fd){??
  26. ????int?val?=?fcntl(fd,?F_GETFL);??
  27. ????val?|=?O_NONBLOCK;??
  28. ????if(fcntl(fd,?F_SETFL,?val)?<?0){??
  29. ????????perror("fcntl?set");??
  30. ????????return?-1;??
  31. ????}??
  32. ????return?0;??
  33. }??
  34. ??
  35. int?main(int?argc,?char?*argv[])??
  36. {??
  37. ????int?sock_fd,?epoll_fd;??
  38. ????struct?epoll_event?event;??
  39. ????struct?epoll_event?*events;??
  40. ??????????
  41. ????if(argc?<?2){??
  42. ????????printf("usage:?[port]?%s",?argv[1]);??
  43. ????????exit(1);??
  44. ????}??
  45. ?????if((sock_fd?=?sock_creat_bind(argv[1]))?<?0){??
  46. ????????perror("socket?and?bind");??
  47. ????????exit(1);??
  48. ????}??
  49. ????if(make_nonblocking(sock_fd)?<?0){??
  50. ????????perror("make?non?blocking");??
  51. ????????exit(1);??
  52. ????}??
  53. ????if(listen(sock_fd,?SOMAXCONN)?<?0){??
  54. ????????perror("listen");??
  55. ????????exit(1);??
  56. ????}??
  57. ????if((epoll_fd?=?epoll_create(MAXEVENTS))<?0){??
  58. ????????perror("epoll_create");??
  59. ????????exit(1);??
  60. ????}??
  61. ????event.data.fd?=?sock_fd;??
  62. ????event.events?=?EPOLLIN;??
  63. ????if(epoll_ctl(epoll_fd,?EPOLL_CTL_ADD,?sock_fd,?&event)?<?0){??
  64. ????????perror("epoll_ctl");??
  65. ????????exit(1);??
  66. ????}??
  67. ????/*buffer?where?events?are?returned*/??
  68. ????events?=?calloc(MAXEVENTS,?sizeof(event));??
  69. ????int?i;??
  70. ????for(i?=?0;?i?<?PROCESS_NUM;?++i){??
  71. ????????int?pid?=?fork();??
  72. ????????if(pid?==?0){??
  73. ????????????while(1){??
  74. ????????????????int?num,?j;??
  75. ????????????????num?=?epoll_wait(epoll_fd,?events,?MAXEVENTS,?-1);??
  76. ????????????????printf("process?%d?returnt?from?epoll_wait\n",?getpid());??
  77. ????????????????sleep(2);??
  78. ????????????????for(i?=?0;?i?<?num;?++i){??
  79. ????????????????????if((events[i].events?&?EPOLLERR)?||?(events[i].events?&?EPOLLHUP)?||?(!(events[i].events?&?EPOLLIN))){??
  80. ????????????????????????fprintf(stderr,?"epoll?error\n");??
  81. ????????????????????????close(events[i].data.fd);??
  82. ????????????????????????continue;??
  83. ????????????????????}else?if(sock_fd?==?events[i].data.fd){??
  84. ????????????????????????//收到關于監聽套接字的通知,意味著一盒或者多個傳入連接??
  85. ????????????????????????struct?sockaddr?in_addr;??
  86. ????????????????????????socklen_t?in_len?=?sizeof(in_addr);??
  87. ????????????????????????if(accept(sock_fd,?&in_addr,?&in_len)?<?0){??
  88. ????????????????????????????printf("process?%d?accept?failed!\n",?getpid());??
  89. ????????????????????????}else{??
  90. ????????????????????????????printf("process?%d?accept?successful!\n",?getpid());??
  91. ????????????????????????}??
  92. ????????????????????}??
  93. ????????????????}??
  94. ????????????}??
  95. ????????}??
  96. ????}??
  97. ????wait(0);??
  98. ????free(events);??
  99. ????close(sock_fd);??
  100. ????return?0;??
  101. }??

上面的代碼編譯gcc epoll_thunder_herd.c -o server?

一個終端運行代碼 ./server 1234? 另一個終端telnet 127.0.0.1 1234

運行結果:

這里我們看到只有一個進程返回了,似乎并沒有驚群效應,讓我們用strace -f? ./server 8888追蹤執行過程(這里只給出telnet之后的截圖,之前的截圖參考accept,不同的就是進程阻塞在epoll_wait)

截圖(部分):

運行結果顯示了部分個進程被喚醒了,返回了“process accept failed”只是后面因為某些原因失敗了。所以這里貌似存在部分“驚群”。

怎么判斷發生了驚群呢?

我們根據strace的返回信息可以確定:

1)系統只會讓一個進程真正的接受這個連接,而剩余的進程會獲得一個EAGAIN信號。圖中有體現。

2)通過返回結果和進程執行的系統調用判斷。

這究竟是什么原因導致的呢?

看我們的代碼,看似部分進程被喚醒了,而事實上其余進程沒有被喚醒的原因是因為某個進程已經處理完這個事件,無需喚醒其他進程,你可以在epoll獲知這個事件的時候sleep(2);這樣所有的進程都會被喚起。看下面改正后的代碼結果更加清晰:

代碼修改:

[cpp]?view plaincopy
  1. num?=?epoll_wait(epoll_fd,?events,?MAXEVENTS,?-1);??
  2. printf("process?%d?returnt?from?epoll_wait\n",?getpid());??
  3. sleep(2);??

運行結果:


如圖所示:所有的進程都被喚醒了。所以epoll_wait的驚群確實存在。

為什么內核處理了accept的驚群,卻不處理epoll_wait的驚群呢?

我想,應該是這樣的:
accept確實應該只能被一個進程調用成功,內核很清楚這一點。但epoll不一樣,他監聽的文件描述符,除了可能后續被accept調用外,還有可能是其他網絡IO事件的,而其他IO事件是否只能由一個進程處理,是不一定的,內核不能保證這一點,這是一個由用戶決定的事情,例如可能一個文件會由多個進程來讀寫。所以,對epoll的驚群,內核則不予處理。

*3)線程驚群:

進程的驚群已經介紹的很詳細了,這里我就舉一個線程驚群的簡單例子,我就截取上次紅包代碼中的代碼片段,如下
[cpp]?view plaincopy
  1. printf("初始的紅包情況:<個數:%d??金額:%d.%02d>\n",item.number,?item.total/100,?item.total%100);??
  2. pthread_cond_broadcast(&temp.cond);//紅包包好后喚醒所有線程搶紅包??
  3. pthread_mutex_unlock(&temp.mutex);//解鎖??
  4. sleep(1);??
沒錯你可能已經注意到了,pthread_cond_broadcast()在資源準備好以后,或者你再編寫程序的時候設置的某個事件滿足時它會喚醒隊列上的所有線程去處理這個事件,但是只有一個線程會真正的獲得事件的“控制權”。
解決方法之一就是加鎖。下面我們來看一看解決或者避免驚群都有哪些方法?

4.我們怎么解決“驚群”呢?你有什么高見?

這里通常代碼加鎖的處理機制我就不詳述了,來看一下常見軟件的處理機制和linux最新的避免和解決的辦法

(1)、Nginx的解決:

如上所述,如果采用epoll,則仍然存在該問題,nginx就是這種場景的一個典型,我們接下來看看其具體的處理方法。
nginx的每個worker進程都會在函數ngx_process_events_and_timers()中處理不同的事件,然后通過ngx_process_events()封裝了不同的事件處理機制,在Linux上默認采用epoll_wait()。
在主要ngx_process_events_and_timers()函數中解決驚群現象。
[cpp]?view plaincopy
  1. void?ngx_process_events_and_timers(ngx_cycle_t?*cycle)??
  2. {??
  3. ????...?...??
  4. ????//?是否通過對accept加鎖來解決驚群問題,需要工作線程數>1且配置文件打開accetp_mutex??
  5. ????if?(ngx_use_accept_mutex)?{??
  6. ????????//?超過配置文件中最大連接數的7/8時,該值大于0,此時滿負荷不會再處理新連接,簡單負載均衡??
  7. ????????if?(ngx_accept_disabled?>?0)?{??
  8. ????????????ngx_accept_disabled--;??
  9. ????????}?else?{??
  10. ????????????//?多個worker僅有一個可以得到這把鎖。獲取鎖不會阻塞過程,而是立刻返回,獲取成功的話??
  11. ????????????//?ngx_accept_mutex_held被置為1。拿到鎖意味著監聽句柄被放到本進程的epoll中了,如果??
  12. ????????????//?沒有拿到鎖,則監聽句柄會被從epoll中取出。??
  13. ????????????if?(ngx_trylock_accept_mutex(cycle)?==?NGX_ERROR)?{??
  14. ????????????????return;??
  15. ????????????}??
  16. ????????????if?(ngx_accept_mutex_held)?{??
  17. ????????????????//?此時意味著ngx_process_events()函數中,任何事件都將延后處理,會把accept事件放到??
  18. ????????????????//?ngx_posted_accept_events鏈表中,epollin|epollout事件都放到ngx_posted_events鏈表中??
  19. ????????????????flags?|=?NGX_POST_EVENTS;??
  20. ????????????}?else?{??
  21. ????????????????//?拿不到鎖,也就不會處理監聽的句柄,這個timer實際是傳給epoll_wait的超時時間,修改??
  22. ????????????????//?為最大ngx_accept_mutex_delay意味著epoll_wait更短的超時返回,以免新連接長時間沒有得到處理??
  23. ????????????????if?(timer?==?NGX_TIMER_INFINITE?||?timer?>?ngx_accept_mutex_delay)?{??
  24. ????????????????????timer?=?ngx_accept_mutex_delay;??
  25. ????????????????}??
  26. ????????????}??
  27. ????????}??
  28. ????}??
  29. ????...?...??
  30. ????(void)?ngx_process_events(cycle,?timer,?flags);???//?實際調用ngx_epoll_process_events函數開始處理??
  31. ????...?...??
  32. ????if?(ngx_posted_accept_events)?{?//如果ngx_posted_accept_events鏈表有數據,就開始accept建立新連接??
  33. ????????ngx_event_process_posted(cycle,?&ngx_posted_accept_events);??
  34. ????}??
  35. ??
  36. ????if?(ngx_accept_mutex_held)?{?//釋放鎖后再處理下面的EPOLLIN?EPOLLOUT請求??
  37. ????????ngx_shmtx_unlock(&ngx_accept_mutex);??
  38. ????}??
  39. ??
  40. ????if?(delta)?{??
  41. ????????ngx_event_expire_timers();??
  42. ????}??
  43. ??
  44. ????ngx_log_debug1(NGX_LOG_DEBUG_EVENT,?cycle->log,?0,?"posted?events?%p",?ngx_posted_events);??
  45. ????//?然后再處理正常的數據讀寫請求。因為這些請求耗時久,所以在ngx_process_events里NGX_POST_EVENTS標??
  46. ????//?志將事件都放入ngx_posted_events鏈表中,延遲到鎖釋放了再處理。??
  47. }}??
具體的解釋參考:nginx處理驚群詳解

(2)、SO_REUSEPORT

Linux內核的3.9版本帶來了SO_REUSEPORT特性,該特性支持多個進程或者線程綁定到同一端口,提高服務器程序的性能,允許多個套接字bind()以及listen()同一個TCP或UDP端口,并且在內核層面實現負載均衡。

在未開啟SO_REUSEPORT的時候,由一個監聽socket將新接收的連接請求交給各個工作者處理,看圖示:


在使用SO_REUSEPORT后,多個進程可以同時監聽同一個IP:端口,然后由內核決定將新鏈接發送給哪個進程,顯然會降低每個工人接收新鏈接時鎖競爭

下面讓我們好好比較一下多進程(線程)服務器編程傳統方法和使用SO_REUSEPORT的區別

運行在Linux系統上的網絡應用程序,為了利用多核的優勢,一般使用以下典型的多進程(多線程)服務器模型:

1.單線程listener/accept,多個工作線程接受任務分發,雖然CPU工作負載不再成為問題,但是仍然存在問題:

? ? ?? (1)、單線程listener(圖一),在處理高速率海量連接的時候,一樣會成為瓶頸

? ? ? ? (2)、cpu緩存行丟失套接字結構現象嚴重。

2.所有工作線程都accept()在同一個服務器套接字上呢?一樣存在問題:

? ? ? ? (1)、多線程訪問server socket鎖競爭嚴重。

? ? ? ? (2)、高負載情況下,線程之間的處理不均衡,有時高達3:1。

? ? ? ? (3)、導致cpu緩存行跳躍(cache line bouncing)。

? ? ? ? (4)、在繁忙cpu上存在較大延遲。

上面兩種方法共同點就是很難做到cpu之間的負載均衡,隨著核數的提升,性能并沒有提升。甚至服務器的吞吐量CPS(Connection Per Second)會隨著核數的增加呈下降趨勢。

下面我們就來看看SO_REUSEPORT解決了什么問題:

? ? ? ? (1)、允許多個套接字bind()/listen()同一個tcp/udp端口。每一個線程擁有自己的服務器套接字,在服務器套接字上沒有鎖的競爭。

? ? ? ? (2)、內核層面實現負載均衡

? ? ? ? (3)、安全層面,監聽同一個端口的套接字只能位于同一個用戶下面。

? ? ? ? (4)、處理新建連接時,查找listener的時候,能夠支持在監聽相同IP和端口的多個sock之間均衡選擇。

當一個連接到來的時候,系統到底是怎么決定那個套接字來處理它?

對于不同內核,存在兩種模式,這兩種模式并不共存,一種叫做熱備份模式,另一種叫做負載均衡模式,3.9內核以后,全部改為負載均衡模式。

熱備份模式:一般而言,會將所有的reuseport同一個IP地址/端口的套接字掛在一個鏈表上,取第一個即可,工作的只有一個,其他的作為備份存在,如果該套接字掛了,它會被從鏈表刪除,然后第二個便會成為第一個。
負載均衡模式:和熱備份模式一樣,所有reuseport同一個IP地址/端口的套接字會掛在一個鏈表上,你也可以認為是一個數組,這樣會更加方便,當有連接到來時,用數據包的源IP/源端口作為一個HASH函數的輸入,將結果對reuseport套接字數量取模,得到一個索引,該索引指示的數組位置對應的套接字便是工作套接字。這樣就可以達到負載均衡的目的,從而降低某個服務的壓力。


編程關于SO_REUSEPORT的詳細介紹請參考:
SO_REUSEPORT?


參考資料:
https://pureage.info/2015/12/22/thundering-herd.html
http://www.tuicool.com/articles/2aumqe
http://blog.163.com/leyni@126/blog/static/16223010220122611523786/
http://baike.baidu.com/link?url=6x0zTazmBxTYE9ngPt_boKjS8ivdQnRlfhHj-STCnqG9tjKwfCluPsKlq-ASUkdQTPW3XrD8FtyilBaI75GJCK
http://m.blog.csdn.net/tuantuanls/article/details/41205739
tcp對so_reuseport的優化?



本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/383721.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383721.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383721.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

epoll原理詳解(最清晰)

https://blog.csdn.net/lyztyycode/article/details/79491419我只是把內容搬運過來做個記錄&#xff0c;方便自己以后回頭看。第一部分&#xff1a;select和epoll的任務關鍵詞&#xff1a;應用程序 文件句柄 用戶態 內核態 監控者要比較epoll相比較select高效在什么地方&#x…

Linux命令【五】系統函數

系統文件函數 stat函數 指針如果沒有const一般表示傳出參數&#xff0c;如果加const表示傳入參數 struct stat dev_t st_dev文件設備編號ino_t st_ino節點 inode號是唯一的&#xff0c;每個inode節點的大小一般是128字節活著256字節&#xff0c;一般文件每2KB就設置一個ino…

生產者-消費者模型的兩種實現方式

https://www.cnblogs.com/caolicangzhu/p/7086176.html本文主要來總結生產者-消費者模型的代碼實現,至于其原理,請大家自行百度. 一、基于鏈表的生產-消費模型(條件變量)我們以鏈表為例,生產者進行頭部插入,消費者進行頭部刪除,因此,先將鏈表相關操作封裝為LinkList.h,具體代碼…

Linux系統【一】CPU+MMU+fork函數創建進程

切板中的內容輸出到文件### 進程相關概念 程序&#xff1a;編譯好的二進制文件&#xff0c;在磁盤上&#xff0c;不占用系統資源&#xff08;不包括磁盤&#xff09;。&#xff08;劇本&#xff09; 進程&#xff1a;占用系統資源&#xff0c;是程序的一次運行。&#xff08;戲…

Ubuntu卸載軟件

用過使用dpkg軟件管理工具得到所有已經安裝的軟件&#xff0c;如果不清楚軟件的全名可以使用grep命令進行查找 然后再使用sudo apt-get remove --purge 軟件名卸載軟件&#xff08;--purge參數會刪除配置文件&#xff0c;刪的干凈一些&#xff09; 例如&#xff1a;

一個重要且實用的signal---SIGCHLD

https://blog.csdn.net/lyztyycode/article/details/78150805SIGCHLD(修改)因為筆者之前的文章里面有錯誤&#xff0c;今天發現&#xff0c;立馬做個修改。在下面我的一段關于sigchld信號相對于直接調用wait函數的好處時&#xff0c;我說調用wait函數要一直檢測子進程是否執行完…

數據結構實驗之鏈表七:單鏈表中重復元素的刪除

https://blog.csdn.net/blessingxry/article/details/794455111.知識點&#xff1a;逆序建立鏈表&#xff0b;節點刪除 2.題意&#xff1a;按照數據輸入的相反順序&#xff08;逆位序&#xff09;建立一個單鏈表&#xff0c;并將單鏈表中重復的元素刪除&#xff08;值相同的元素…

Python3函數和代碼復用

函數的定義 def 函數名([參數列表]):注釋函數體注意事項 函數形參不需要聲明類型&#xff0c;可以使用return語句在結束函數執行的同時返回任意類型的值&#xff0c;函數返回值類型與return語句返回表達式i的類型一致 即使該函數不需要接受任何參數&#xff0c;也必須保留一堆…

一文說盡C++賦值運算符重載函數(operator=)

http://www.cnblogs.com/zpcdbky/p/5027481.html在前面&#xff1a;關于C的賦值運算符重載函數(operator)&#xff0c;網絡以及各種教材上都有很多介紹&#xff0c;但可惜的是&#xff0c;內容大多雷同且不全面。面對這一局面&#xff0c;在下在整合各種資源及融入個人理解的基…

Python a和a[:]的區別

簡單來講a[:]是深復制&#xff0c;a是淺復制&#xff0c;相當于賦值a的話是賦值了指針&#xff0c;賦值a[:]相當于復制了a對應的那段空間 例如&#xff1a; a [1,1,1,1,1,1]for x in a:if x1:a.remove(x)print(a)運行結果&#xff1a; remove操作是移除序列中第一個x元素。…

約瑟夫環(c語言程序完整版)

https://blog.csdn.net/m_hahahaha1994/article/details/51742453約瑟夫環&#xff08;約瑟夫問題&#xff09;是一個數學的應用問題&#xff1a;已知n個人&#xff08;以編號1&#xff0c;2&#xff0c;3…n分別表示&#xff09;圍坐在一張圓桌周圍。從編號為k的人開始報數&am…

Linux系統【二】exec族函數及應用

文件描述符 文件描述符表是一個指針數組&#xff0c;文件描述符是一個整數。 文件描述符表對應的指針是一個結構體&#xff0c;名字為file_struct&#xff0c;里面保存的是已經打開文件的信息 需要注意的是父子進程之間讀時共享&#xff0c;寫時復制的原則是針對物理地址而言…

白話C++系列(27) -- RTTI:運行時類型識別

http://www.cnblogs.com/kkdd-2013/p/5601783.htmlRTTI—運行時類型識別 RTTI&#xff1a;Run-Time Type Identification。 那么RTTI如何來體現呢&#xff1f;這就要涉及到typeid和dynamic_cast這兩個知識點了。為了更好的去理解&#xff0c;那么我們就通過一個例子來說明。這個…

使用頭文件的原因和規范

原因 通過頭文件來調用庫功能。在很多場合&#xff0c;源代碼不便&#xff08;或不準&#xff09;向用戶公布&#xff0c;只 要向用戶提供頭文件和二進制的庫即可。用戶只需要按照頭文件中的接口聲明來調用庫 功能&#xff0c;而不必關心接口怎么實現的。編譯器會從庫中提取相應…

轉圈踢人問題

https://www.cnblogs.com/lanxuezaipiao/p/3339603.html 有N個人圍一圈依次報數&#xff0c;數到3的倍數的人出列&#xff0c;問當只剩一個人時他原來的位子在哪里&#xff1f; 解答&#xff1a;經典的轉圈踢人問題&#xff0c;好吧專業一點&#xff0c;約瑟夫環問題&#xff0…

Linux系統【三】回收子進程

孤兒進程 父進程先于子進程結束&#xff0c;則子進程成為孤兒進程&#xff0c;子進程的父進程成為init進程&#xff0c;則稱init進程領養孤兒進程。現在好像是用戶進程中的system進程。 僵尸進程 進程終止&#xff0c;父進程不進行回收&#xff0c;自己成殘留資源(PCB)存放在…

string類的基本實現

https://blog.csdn.net/qq_29503203/article/details/52265829在面試中面試官常常會讓你寫出string類的基本操作&#xff0c;比如&#xff1a;構造函數&#xff0c;析構函數&#xff0c;拷貝構造等等.下面是除此之外的一些操作&#xff0c;希望可以幫助你更好的理解string以便以…

Python3常用數據結構

Python3中有三種組合數據類型&#xff0c;分別為&#xff1a; 序列類型&#xff1a;字符串&#xff08;str&#xff09;、元組&#xff08;tuple&#xff09;、列表&#xff08;list&#xff09;集合類型&#xff1a;集合&#xff08;set&#xff09;映射類型&#xff1a;字典…

Linux C++ 回射服務器

http://blog.csdn.net/qq_25425023/article/details/53914820回射服務器就是服務端將客戶端的數據發送回去。我實現的回射服務器返回增加了時間。服務端代碼&#xff0c;可以很容易看懂&#xff1a;[cpp] view plaincopy#include <sys/socket.h> #include <stdio.h&g…

TCP第四次揮手為什么要等待2MSL

當客戶端進入TIME-WAIT狀態的時候(也就是第四次揮手的時候)&#xff0c;必須經過時間計數器設置的時間2MSL(最長報文段壽命)后&#xff0c;才能進入關閉狀態&#xff0c;這時為什么呢&#xff1f;&#xff1f;&#xff1f; 這最主要是因為兩個理由&#xff1a; 1、為了保證客戶…