Nginx引入執行緒池 效能提升9倍
1. 引言
正如我們所知,NGINX採用了非同步、事件驅動的方法來處理連線。這種處理方式無需(像使用傳統架構的伺服器一樣)為每個請求建立額外的專用程式或者執行緒,而是在一個工作程式中處理多個連線和請求。為此,NGINX工作在非阻塞的socket模式下,並使用了epoll 和 kqueue這樣有效的方法。
因為滿負載程式的數量很少(通常每核CPU只有一個)而且恆定,所以任務切換隻消耗很少的記憶體,而且不會浪費CPU週期。通過NGINX本身的例項,這種方法的優點已經為眾人所知。NGINX可以非常好地處理百萬級規模的併發請求。
每個程式都消耗額外的記憶體,而且每次程式間的切換都會消耗CPU週期並丟棄CPU快取記憶體中的資料。
但是,非同步、事件驅動方法仍然存在問題。或者,我喜歡將這一問題稱為“敵兵”,這個敵兵的名字叫阻塞(blocking)。不幸的是,很多第三方模組使用了阻塞呼叫,然而使用者(有時甚至是模組的開發者)並不知道阻塞的缺點。阻塞操作可以毀掉NGINX的效能,我們必須不惜一切代價避免使用阻塞。
即使在當前官方的NGINX程式碼中,依然無法在全部場景中避免使用阻塞,NGINX1.7.11中實現的執行緒池機制解決了這個問題。我們將在後面講述這個執行緒池是什麼以及該如何使用。現在,讓我們先和我們的“敵兵”進行一次面對面的碰撞。
2. 問題
首先,為了更好地理解這一問題,我們用幾句話說明下NGINX是如何工作的。
通常情況下,NGINX是一個事件處理器,即一個接收來自核心的所有連線事件的資訊,然後向作業系統發出做什麼指令的控制器。實際上,NGINX幹了編排作業系統的全部髒活累活,而作業系統做的是讀取和傳送位元組這樣的日常工作。所以,對於NGINX來說,快速和及時的響應是非常重要的。
工作程式監聽並處理來自核心的事件
事件可以是超時、socket讀寫就緒的通知,或者發生錯誤的通知。NGINX接收大量的事件,然後一個接一個地處理它們,並執行必要的操作。因此,所有的處理過程是通過一個執行緒中的佇列,在一個簡單迴圈中完成的。NGINX從佇列中取出一個事件並對其做出響應,比如讀寫socket。在多數情況下,這種方式是非常快的(也許只需要幾個CPU週期,將一些資料複製到記憶體中),NGINX可以在一瞬間處理掉佇列中的所有事件。
所有處理過程是在一個簡單的迴圈中,由一個執行緒完成
但是,如果NGINX要處理的操作是一些又長又重的操作,又會發生什麼呢?整個事件處理迴圈將會卡住,等待這個操作執行完畢。
因此,所謂“阻塞操作”是指任何導致事件處理迴圈顯著停止一段時間的操作。操作可以由於各種原因成為阻塞操作。例如,NGINX可能因長時間、CPU密集型處理,或者可能等待訪問某個資源(比如硬碟,或者一個互斥體,亦或要從處於同步方式的資料庫獲得相應的庫函式呼叫等)而繁忙。關鍵是在處理這樣的操作期間,工作程式無法做其他事情或者處理其他事件,即使有更多的可用系統資源可以被佇列中的一些事件所利用。
我們來打個比方,一個商店的營業員要接待他面前排起的一長隊顧客。隊伍中的第一位顧客想要的某件商品不在店裡而在倉庫中。這位營業員跑去倉庫把東西拿來。現在整個隊伍必須為這樣的配貨方式等待數個小時,隊伍中的每個人都很不爽。你可以想見人們的反應吧?隊伍中每個人的等待時間都要增加這些時間,除非他們要買的東西就在店裡。
隊伍中的每個人不得不等待第一個人的購買
在NGINX中會發生幾乎同樣的情況,比如當讀取一個檔案的時候,如果該檔案沒有快取在記憶體中,就要從磁碟上讀取。從磁碟(特別是旋轉式的磁碟)讀取是很慢的,而當佇列中等待的其他請求可能不需要訪問磁碟時,它們也得被迫等待。導致的結果是,延遲增加並且系統資源沒有得到充分利用。
一個阻塞操作足以顯著地延緩所有接下來的操作
一些作業系統為讀寫檔案提供了非同步介面,NGINX可以使用這樣的介面(見AIO指令)。FreeBSD就是個很好的例子。不幸的是,我們不能在Linux上得到相同的福利。雖然Linux為讀取檔案提供了一種非同步介面,但是存在明顯的缺點。其中之一是要求檔案訪問和緩衝要對齊,但NGINX很好地處理了這個問題。但是,另一個缺點更糟糕。非同步介面要求檔案描述符中要設定O_DIRECT標記,就是說任何對檔案的訪問都將繞過記憶體中的快取,這增加了磁碟的負載。在很多場景中,這都絕對不是最佳選擇。
為了有針對性地解決這一問題,在NGINX 1.7.11中引入了執行緒池。預設情況下,NGINX+還沒有包含執行緒池,但是如果你想試試的話,可以聯絡銷售人員,NGINX+ R6是一個已經啟用了執行緒池的構建版本。
現在,讓我們走進執行緒池,看看它是什麼以及如何工作的。
3. 執行緒池
讓我們回到那個可憐的,要從大老遠的倉庫去配貨的售貨員那兒。這回,他已經變聰明瞭(或者也許是在一群憤怒的顧客教訓了一番之後,他才變得聰明的?),僱用了一個配貨服務團隊。現在,當任何人要買的東西在大老遠的倉庫時,他不再親自去倉庫了,只需要將訂單丟給配貨服務,他們將處理訂單,同時,我們的售貨員依然可以繼續為其他顧客服務。因此,只有那些要買倉庫裡東西的顧客需要等待配貨,其他顧客可以得到即時服務。
傳遞訂單給配貨服務不會阻塞隊伍
對NGINX而言,執行緒池執行的就是配貨服務的功能。它由一個任務佇列和一組處理這個佇列的執行緒組成。
當工作程式需要執行一個潛在的長操作時,工作程式不再自己執行這個操作,而是將任務放到執行緒池佇列中,任何空閒的執行緒都可以從佇列中獲取並執行這個任務。
工作程式將阻塞操作卸給執行緒池
那麼,這就像我們有了另外一個佇列。是這樣的,但是在這個場景中,佇列受限於特殊的資源。磁碟的讀取速度不能比磁碟產生資料的速度快。不管怎麼說,至少現在磁碟不再延誤其他事件,只有訪問檔案的請求需要等待。
“從磁碟讀取”這個操作通常是阻塞操作最常見的示例,但是實際上,NGINX中實現的執行緒池可用於處理任何不適合在主迴圈中執行的任務。
目前,解除安裝到執行緒池中執行的兩個基本操作是大多數作業系統中的read()系統呼叫和Linux中的sendfile()。接下來,我們將對執行緒池進行測試(test)和基準測試(benchmark),在未來的版本中,如果有明顯的優勢,我們可能會解除安裝其他操作到執行緒池中。
4. 基準測試
現在讓我們從理論過度到實踐。我們將進行一次模擬基準測試(synthetic benchmark),模擬在阻塞操作和非阻塞操作的最差混合條件下,使用執行緒池的效果。
另外,我們需要一個記憶體肯定放不下的資料集。在一臺48GB記憶體的機器上,我們已經產生了每檔案大小為4MB的隨機資料,總共256GB,然後配置NGINX,版本為1.9.0。
配置很簡單:
worker_processes 16; events { accept_mutex off; } http { include mime.types; default_type application/octet-stream; access_log off; sendfile on; sendfile_max_chunk 512k; server { listen 8000; location / { root /storage; } } }
如上所示,為了達到更好的效能,我們調整了幾個引數:禁用了logging和accept_mutex,同時,啟用了sendfile並設定了sendfile_max_chunk的大小。最後一個指令可以減少阻塞呼叫sendfile()所花費的最長時間,因為NGINX不會嘗試一次將整個檔案傳送出去,而是每次傳送大小為512KB的塊資料。
這臺測試伺服器有2個Intel Xeon E5645處理器(共計:12核、24超執行緒)和10-Gbps的網路介面。磁碟子系統是由4塊西部資料WD1003FBYX 磁碟組成的RAID10陣列。所有這些硬體由Ubuntu伺服器14.04.1 LTS供電。
為基準測試配置負載生成器和NGINX
客戶端有2臺伺服器,它們的規格相同。在其中一臺上,在wrk中使用Lua指令碼建立了負載程式。指令碼使用200個並行連線向伺服器請求檔案,每個請求都可能未命中快取而從磁碟阻塞讀取。我們將這種負載稱作隨機負載。
在另一臺客戶端機器上,我們將執行wrk的另一個副本,使用50個並行連線多次請求同一個檔案。因為這個檔案將被頻繁地訪問,所以它會一直駐留在記憶體中。在正常情況下,NGINX能夠非常快速地服務這些請求,但是如果工作程式被其他請求阻塞的話,效能將會下降。我們將這種負載稱作恆定負載。
效能將由伺服器上ifstat監測的吞吐率(throughput)和從第二臺客戶端獲取的wrk結果來度量。
現在,沒有使用執行緒池的第一次執行將不會帶給我們非常振奮的結果:
% ifstat -bi eth2 eth2 Kbps in Kbps out 5531.24 1.03e+06 4855.23 812922.7 5994.66 1.07e+06 5476.27 981529.3 6353.62 1.12e+06 5166.17 892770.3 5522.81 978540.8 6208.10 985466.7 6370.79 1.12e+06 6123.33 1.07e+06
如上所示,使用這種配置,伺服器產生的總流量約為1Gbps。從下面所示的top輸出,我們可以看到,工作程式的大部分時間花在阻塞I/O上(它們處於top的D狀態):
top - 10:40:47 up 11 days, 1:32, 1 user, load average: 49.61, 45.77 62.89 Tasks: 375 total, 2 running, 373 sleeping, 0 stopped, 0 zombie %Cpu(s): 0.0 us, 0.3 sy, 0.0 ni, 67.7 id, 31.9 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem: 49453440 total, 49149308 used, 304132 free, 98780 buffers KiB Swap: 10474236 total, 20124 used, 10454112 free, 46903412 cached Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 4639 vbart 20 0 47180 28152 496 D 0.7 0.1 0:00.17 nginx 4632 vbart 20 0 47180 28196 536 D 0.3 0.1 0:00.11 nginx 4633 vbart 20 0 47180 28324 540 D 0.3 0.1 0:00.11 nginx 4635 vbart 20 0 47180 28136 480 D 0.3 0.1 0:00.12 nginx 4636 vbart 20 0 47180 28208 536 D 0.3 0.1 0:00.14 nginx 4637 vbart 20 0 47180 28208 536 D 0.3 0.1 0:00.10 nginx 4638 vbart 20 0 47180 28204 536 D 0.3 0.1 0:00.12 nginx 4640 vbart 20 0 47180 28324 540 D 0.3 0.1 0:00.13 nginx 4641 vbart 20 0 47180 28324 540 D 0.3 0.1 0:00.13 nginx 4642 vbart 20 0 47180 28208 536 D 0.3 0.1 0:00.11 nginx 4643 vbart 20 0 47180 28276 536 D 0.3 0.1 0:00.29 nginx 4644 vbart 20 0 47180 28204 536 D 0.3 0.1 0:00.11 nginx 4645 vbart 20 0 47180 28204 536 D 0.3 0.1 0:00.17 nginx 4646 vbart 20 0 47180 28204 536 D 0.3 0.1 0:00.12 nginx 4647 vbart 20 0 47180 28208 532 D 0.3 0.1 0:00.17 nginx 4631 vbart 20 0 47180 756 252 S 0.0 0.1 0:00.00 nginx 4634 vbart 20 0 47180 28208 536 D 0.0 0.1 0:00.11 nginx 4648 vbart 20 0 25232 1956 1160 R 0.0 0.0 0:00.08 top 25921 vbart 20 0 121956 2232 1056 S 0.0 0.0 0:01.97 sshd 25923 vbart 20 0 40304 4160 2208 S 0.0 0.0 0:00.53 zsh
在這種情況下,吞吐率受限於磁碟子系統,而CPU在大部分時間裡是空閒的。從wrk獲得的結果也非常低:
Running 1m test @ http://192.0.2.1:8000/1/1/1 12 threads and 50 connections Thread Stats Avg Stdev Max +/- Stdev Latency 7.42s 5.31s 24.41s 74.73% Req/Sec 0.15 0.36 1.00 84.62% 488 requests in 1.01m, 2.01GB read Requests/sec: 8.08 Transfer/sec: 34.07MB
請記住,檔案是從記憶體送達的!第一個客戶端的200個連線建立的隨機負載,使伺服器端的全部的工作程式忙於從磁碟讀取檔案,因此產生了過大的延遲,並且無法在合理的時間內處理我們的請求。
現在,我們的執行緒池要登場了。為此,我們只需在location塊中新增aio threads指令:
location / { root /storage; aio threads; }
接著,執行NGINX reload重新載入配置。
然後,我們重複上述的測試:
% ifstat -bi eth2 eth2 Kbps in Kbps out 60915.19 9.51e+06 59978.89 9.51e+06 60122.38 9.51e+06 61179.06 9.51e+06 61798.40 9.51e+06 57072.97 9.50e+06 56072.61 9.51e+06 61279.63 9.51e+06 61243.54 9.51e+06 59632.50 9.50e+06
現在,我們的伺服器產生的流量是9.5Gbps,相比之下,沒有使用執行緒池時只有約1Gbps!
理論上還可以產生更多的流量,但是這已經達到了機器的最大網路吞吐能力,所以在這次NGINX的測試中,NGINX受限於網路介面。工作程式的大部分時間只是休眠和等待新的時間(它們處於top的S狀態):
top - 10:43:17 up 11 days, 1:35, 1 user, load average: 172.71, 93.84, 77.90 Tasks: 376 total, 1 running, 375 sleeping, 0 stopped, 0 zombie %Cpu(s): 0.2 us, 1.2 sy, 0.0 ni, 34.8 id, 61.5 wa, 0.0 hi, 2.3 si, 0.0 st KiB Mem: 49453440 total, 49096836 used, 356604 free, 97236 buffers KiB Swap: 10474236 total, 22860 used, 10451376 free, 46836580 cached Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 4654 vbart 20 0 309708 28844 596 S 9.0 0.1 0:08.65 nginx 4660 vbart 20 0 309748 28920 596 S 6.6 0.1 0:14.82 nginx 4658 vbart 20 0 309452 28424 520 S 4.3 0.1 0:01.40 nginx 4663 vbart 20 0 309452 28476 572 S 4.3 0.1 0:01.32 nginx 4667 vbart 20 0 309584 28712 588 S 3.7 0.1 0:05.19 nginx 4656 vbart 20 0 309452 28476 572 S 3.3 0.1 0:01.84 nginx 4664 vbart 20 0 309452 28428 524 S 3.3 0.1 0:01.29 nginx 4652 vbart 20 0 309452 28476 572 S 3.0 0.1 0:01.46 nginx 4662 vbart 20 0 309552 28700 596 S 2.7 0.1 0:05.92 nginx 4661 vbart 20 0 309464 28636 596 S 2.3 0.1 0:01.59 nginx 4653 vbart 20 0 309452 28476 572 S 1.7 0.1 0:01.70 nginx 4666 vbart 20 0 309452 28428 524 S 1.3 0.1 0:01.63 nginx 4657 vbart 20 0 309584 28696 592 S 1.0 0.1 0:00.64 nginx 4655 vbart 20 0 30958 28476 572 S 0.7 0.1 0:02.81 nginx 4659 vbart 20 0 309452 28468 564 S 0.3 0.1 0:01.20 nginx 4665 vbart 20 0 309452 28476 572 S 0.3 0.1 0:00.71 nginx 5180 vbart 20 0 25232 1952 1156 R 0.0 0.0 0:00.45 top 4651 vbart 20 0 20032 752 252 S 0.0 0.0 0:00.00 nginx 25921 vbart 20 0 121956 2176 1000 S 0.0 0.0 0:01.98 sshd 25923 vbart 20 0 40304 3840 2208 S 0.0 0.0 0:00.54 zsh
如上所示,基準測試中還有大量的CPU資源剩餘。
wrk的結果如下:
Running 1m test @ http://192.0.2.1:8000/1/1/1 12 threads and 50 connections Thread Stats Avg Stdev Max +/- Stdev Latency 226.32ms 392.76ms 1.72s 93.48% Req/Sec 20.02 10.84 59.00 65.91% 15045 requests in 1.00m, 58.86GB read Requests/sec: 250.57 Transfer/sec: 0.98GB
伺服器處理4MB檔案的平均時間從7.42秒降到226.32毫秒(減少了33倍),每秒請求處理數提升了31倍(250 vs 8)!
對此,我們的解釋是請求不再因為工作程式被阻塞在讀檔案,而滯留在事件佇列中,等待處理,它們可以被空閒的程式處理掉。只要磁碟子系統能做到最好,就能服務好第一個客戶端上的隨機負載,NGINX可以使用剩餘的CPU資源和網路容量,從記憶體中讀取,以服務於上述的第二個客戶端的請求。
5. 依然沒有銀彈
在丟擲我們對阻塞操作的擔憂並給出一些令人振奮的結果後,可能大部分人已經打算在你的伺服器上配置執行緒池了。先彆著急。
實際上,最幸運的情況是,讀取和傳送檔案操作不去處理緩慢的硬碟驅動器。如果我們有足夠多的記憶體來儲存資料集,那麼作業系統將會足夠聰明地在被稱作“頁面快取”的地方,快取頻繁使用的檔案。
“頁面快取”的效果很好,可以讓NGINX在幾乎所有常見的用例中展示優異的效能。從頁面快取中讀取比較快,沒有人會說這種操作是“阻塞”。而另一方面,解除安裝任務到一個執行緒池是有一定開銷的。
因此,如果記憶體有合理的大小並且待處理的資料集不是很大的話,那麼無需使用執行緒池,NGINX已經工作在最優化的方式下。
解除安裝讀操作到執行緒池是一種適用於非常特殊任務的技術。只有當經常請求的內容的大小,不適合作業系統的虛擬機器快取時,這種技術才是最有用的。至於可能適用的場景,比如,基於NGINX的高負載流媒體伺服器。這正是我們已經模擬的基準測試的場景。
我們如果可以改進解除安裝讀操作到執行緒池,將會非常有意義。我們只需要知道所需的檔案資料是否在記憶體中,只有不在記憶體中時,讀操作才應該解除安裝到一個單獨的執行緒中。
再回到售貨員那個比喻的場景中,這回,售貨員不知道要買的商品是否在店裡,他必須要麼總是將所有的訂單提交給配貨服務,要麼總是親自處理它們。
人艱不拆,作業系統缺少這樣的功能。第一次嘗試是在2010年,人們試圖將這一功能新增到Linux作為fincore()系統呼叫,但是沒有成功。後來還有一些嘗試,是使用RWF_NONBLOCK標記作為preadv2()系統呼叫來實現這一功能(詳情見LWN.net上的非阻塞緩衝檔案讀取操作和非同步緩衝讀操作)。但所有這些補丁的命運目前還不明朗。悲催的是,這些補丁尚沒有被核心接受的主要原因,貌似是因為曠日持久的撕逼大戰(bikeshedding)。
另一方面,FreeBSD的使用者完全不必擔心。FreeBSD已經具備足夠好的讀檔案取非同步介面,我們應該用這個介面而不是執行緒池。
6. 配置執行緒池
所以,如果你確信在你的場景中使用執行緒池可以帶來好處,那麼現在是時候深入瞭解執行緒池的配置了。
執行緒池的配置非常簡單、靈活。首先,獲取NGINX 1.7.11或更高版本的原始碼,使用–with-threads配置引數編譯。在最簡單的場景中,配置看起來很樸實。我們只需要在http、 server,或者location上下文中包含aio threads指令即可:
aio threads;
這是執行緒池的最簡配置。實際上的精簡版本示例如下:
thread_pool default threads=32 max_queue=65536; aio threads=default;
這裡定義了一個名為“default”,包含32個執行緒,任務佇列最多支援65536個請求的執行緒池。如果任務佇列過載,NGINX將輸出如下錯誤日誌並拒絕請求:
thread pool "NAME" queue overflow: N tasks waiting
錯誤輸出意味著執行緒處理作業的速度有可能低於任務入隊的速度了。你可以嘗試增加佇列的最大值,但是如果這無濟於事,那麼這說明你的系統沒有能力處理如此多的請求了。
正如你已經注意到的,你可以使用thread_pool指令,配置執行緒的數量、佇列的最大值,以及執行緒池的名稱。最後要說明的是,可以配置多個獨立的執行緒池,將它們置於不同的配置檔案中,用做不同的目的:
http { thread_pool one threads=128 max_queue=0; thread_pool two threads=32; server { location /one { aio threads=one; } location /two { aio threads=two; } } … }
如果沒有指定max_queue引數的值,預設使用的值是65536。如上所示,可以設定max_queue為0。在這種情況下,執行緒池將使用配置中全部數量的執行緒,儘可能地同時處理多個任務;佇列中不會有等待的任務。
現在,假設我們有一臺伺服器,掛了3塊硬碟,我們希望把該伺服器用作“快取代理”,快取後端伺服器的全部響應資訊。預期的快取資料量遠大於可用的記憶體。它實際上是我們個人CDN的一個快取節點。毫無疑問,在這種情況下,最重要的事情是發揮硬碟的最大效能。
我們的選擇之一是配置一個RAID陣列。這種方法譭譽參半,現在,有了NGINX,我們可以有其他的選擇:
# 我們假設每塊硬碟掛載在相應的目錄中:/mnt/disk1、/mnt/disk2、/mnt/disk3 proxy_cache_path /mnt/disk1 levels=1:2 keys_zone=cache_1:256m max_size=1024G use_temp_path=off; proxy_cache_path /mnt/disk2 levels=1:2 keys_zone=cache_2:256m max_size=1024G use_temp_path=off; proxy_cache_path /mnt/disk3 levels=1:2 keys_zone=cache_3:256m max_size=1024G use_temp_path=off; thread_pool pool_1 threads=16; thread_pool pool_2 threads=16; thread_pool pool_3 threads=16; split_clients $request_uri $disk { 33.3% 1; 33.3% 2; * 3; } location / { proxy_pass http://backend; proxy_cache_key $request_uri; proxy_cache cache_$disk; aio threads=pool_$disk; sendfile on; }
在這份配置中,使用了3個獨立的快取,每個快取專用一塊硬碟,另外,3個獨立的執行緒池也各自專用一塊硬碟。
快取之間(其結果就是磁碟之間)的負載均衡使用split_clients模組,split_clients非常適用於這個任務。
在 proxy_cache_path指令中設定use_temp_path=off,表示NGINX會將臨時檔案儲存在快取資料的同一目錄中。這是為了避免在更新快取時,磁碟之間互相複製響應資料。
這些調優將帶給我們磁碟子系統的最大效能,因為NGINX通過單獨的執行緒池並行且獨立地與每塊磁碟互動。每塊磁碟由16個獨立執行緒和讀取和傳送檔案專用任務佇列提供服務。
我敢打賭,你的客戶喜歡這種量身定製的方法。請確保你的磁碟也持有同樣的觀點。
這個示例很好地證明了NGINX可以為硬體專門調優的靈活性。這就像你給NGINX下了一道命令,讓機器和資料用最佳姿勢來搞基。而且,通過NGINX在使用者空間中細粒度的調優,我們可以確保軟體、作業系統和硬體工作在最優模式下,儘可能有效地利用系統資源。
7. 總結
綜上所述,執行緒池是一個偉大的功能,將NGINX推向了新的效能水平,除掉了一個眾所周知的長期危害——阻塞——尤其是當我們真正面對大量內容的時候。
甚至,還有更多的驚喜。正如前面提到的,這個全新的介面,有可能沒有任何效能損失地解除安裝任何長期阻塞操作。NGINX在擁有大量的新模組和新功能方面,開闢了一方新天地。許多流行的庫仍然沒有提供非同步非阻塞介面,此前,這使得它們無法與NGINX相容。我們可以花大量的時間和資源,去開發我們自己的無阻塞原型庫,但這麼做始終都是值得的嗎?現在,有了執行緒池,我們可以相對容易地使用這些庫,而不會影響這些模組的效能。
相關文章
- Nginx 引入執行緒池,提升 9 倍效能Nginx執行緒
- 高效C#程式設計:透過智慧執行緒池管理提升效能C#程式設計執行緒
- Java執行緒池二:執行緒池原理Java執行緒
- 淺談執行緒池(上):執行緒池的作用及CLR執行緒池執行緒
- 執行緒和執行緒池執行緒
- 多執行緒【執行緒池】執行緒
- 執行緒 執行緒池 Task執行緒
- 執行緒池執行緒
- 淺談執行緒池(中):獨立執行緒池的作用及IO執行緒池執行緒
- Java多執行緒——執行緒池Java執行緒
- java執行緒池趣味事:這不是執行緒池Java執行緒
- 執行緒池以及四種常見執行緒池執行緒
- java--執行緒池--建立執行緒池的幾種方式與執行緒池操作詳解Java執行緒
- java多執行緒9:執行緒池Java執行緒
- 二. 執行緒管理之執行緒池執行緒
- kuangshenshuo-多執行緒-執行緒池執行緒
- 執行緒的建立及執行緒池執行緒
- JavaThread多執行緒執行緒池Javathread執行緒
- Java多執行緒18:執行緒池Java執行緒
- 多執行緒之手撕執行緒池執行緒
- 執行緒池管理(1)-為什麼需要執行緒池執行緒
- epoll程式設計,單epoll+執行緒池?執行緒池+epoll?nginx實現高併發的原理?程式設計執行緒Nginx
- 執行緒與執行緒池的那些事之執行緒池篇(萬字長文)執行緒
- 執行緒池 Executor執行緒
- Java執行緒池Java執行緒
- java 執行緒池Java執行緒
- 再聊執行緒池執行緒
- 執行緒池原理執行緒
- Ruby執行緒池執行緒
- Android多執行緒之執行緒池Android執行緒
- Java多執行緒-執行緒池的使用Java執行緒
- 執行緒池建立執行緒的過程執行緒
- Java執行緒池一:執行緒基礎Java執行緒
- Android的執行緒和執行緒池Android執行緒
- 【Java】【多執行緒】執行緒池簡述Java執行緒
- java多執行緒系列之執行緒池Java執行緒
- golang workpool,工作池,執行緒池Golang執行緒
- 執行緒執行順序——CountDownLatch、CyclicBarrier 、join()、執行緒池執行緒CountDownLatch