• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            陳碩的Blog

            Muduo 多線程模型:一個(gè) Sudoku 服務(wù)器演變

            陳碩 (giantchen AT gmail)

            blog.csdn.net/Solstice

            Muduo 全系列文章列表: http://blog.csdn.net/Solstice/category/779646.aspx

            本文以一個(gè) Sudoku Solver 為例,回顧了并發(fā)網(wǎng)絡(luò)服務(wù)程序的多種設(shè)計(jì)方案,并介紹了使用 muduo 網(wǎng)絡(luò)庫編寫多線程服務(wù)器的兩種最常用手法。以往的例子展現(xiàn)了 Muduo 在編寫單線程并發(fā)網(wǎng)絡(luò)服務(wù)程序方面的能力與便捷性,今天我們看一看它在多線程方面的表現(xiàn)。

            本文代碼見:http://code.google.com/p/muduo/source/browse/trunk/examples/sudoku/

            Sudoku Solver

            假設(shè)有這么一個(gè)網(wǎng)絡(luò)編程任務(wù):寫一個(gè)求解數(shù)獨(dú)的程序 (Sudoku Solver),并把它做成一個(gè)網(wǎng)絡(luò)服務(wù)。

            Sudoku Solver 是我喜愛的網(wǎng)絡(luò)編程例子,它曾經(jīng)出現(xiàn)在《分布式系統(tǒng)部署、監(jiān)控與進(jìn)程管理的幾重境界》、《Muduo 設(shè)計(jì)與實(shí)現(xiàn)之一:Buffer 類的設(shè)計(jì)》、《〈多線程服務(wù)器的適用場合〉例釋與答疑》等文中,它也可以看成是 echo 服務(wù)的一個(gè)變種(《談一談網(wǎng)絡(luò)編程學(xué)習(xí)經(jīng)驗(yàn)》把 echo 列為三大 TCP 網(wǎng)絡(luò)編程案例之一)。

            寫這么一個(gè)程序在網(wǎng)絡(luò)編程方面的難度不高,跟寫 echo 服務(wù)差不多(從網(wǎng)絡(luò)連接讀入一個(gè) Sudoku 題目,算出答案,再發(fā)回給客戶),挑戰(zhàn)在于怎樣做才能發(fā)揮現(xiàn)在多核硬件的能力?在談這個(gè)問題之前,讓我們先寫一個(gè)基本的單線程版。

            協(xié)議

            一個(gè)簡單的以 \r\n 分隔的文本行協(xié)議,使用 TCP 長連接,客戶端在不需要服務(wù)時(shí)主動斷開連接。

            請求:[id:]〈81digits〉\r\n

            響應(yīng):[id:]〈81digits〉\r\n 或者 [id:]NoSolution\r\n

            其中[id:]表示可選的 id,用于區(qū)分先后的請求,以支持 Parallel Pipelining,響應(yīng)中會回顯請求中的 id。Parallel Pipelining 的意義見賴勇浩的《以小見大——那些基于 protobuf 的五花八門的 RPC(2) 》,或者見我寫的《分布式系統(tǒng)的工程化開發(fā)方法》第 54 頁關(guān)于 out-of-order RPC 的介紹。

            〈81digits〉是 Sudoku 的棋盤,9x9 個(gè)數(shù)字,未知數(shù)字以 0 表示。

            如果 Sudoku 有解,那么響應(yīng)是填滿數(shù)字的棋盤;如果無解,則返回 NoSolution。

            例子1:

            請求:000000010400000000020000000000050407008000300001090000300400200050100000000806000\r\n

            響應(yīng):693784512487512936125963874932651487568247391741398625319475268856129743274836159\r\n

            例子2:

            請求:a:000000010400000000020000000000050407008000300001090000300400200050100000000806000\r\n

            響應(yīng):a:693784512487512936125963874932651487568247391741398625319475268856129743274836159\r\n

            例子3:

            請求:b:000000010400000000020000000000050407008000300001090000300400200050100000000806005\r\n

            響應(yīng):b:NoSolution\r\n

            基于這個(gè)文本協(xié)議,我們可以用 telnet 模擬客戶端來測試 sudoku solver,不需要單獨(dú)編寫 sudoku client。SudokuSolver 的默認(rèn)端口號是 9981,因?yàn)樗?9x9=81 個(gè)格子。

            基本實(shí)現(xiàn)

            Sudoku 的求解算法見《談?wù)剶?shù)獨(dú)(Sudoku)》一文,這不是本文的重點(diǎn)。假設(shè)我們已經(jīng)有一個(gè)函數(shù)能求解 Sudoku,它的原型如下

            string solveSudoku(const string& puzzle);

            函數(shù)的輸入是上文的"〈81digits〉",輸出是"〈81digits〉"或"NoSolution"。這個(gè)函數(shù)是個(gè) pure function,同時(shí)也是線程安全的。

            有了這個(gè)函數(shù),我們以《Muduo 網(wǎng)絡(luò)編程示例之零:前言》中的 EchoServer 為藍(lán)本,稍作修改就能得到 SudokuServer。這里只列出最關(guān)鍵的 onMessage() 函數(shù),完整的代碼見 http://code.google.com/p/muduo/source/browse/trunk/examples/sudoku/server_basic.cc 。onMessage() 的主要功能是處理協(xié)議格式,并調(diào)用 solveSudoku() 求解問題。

             // muduo/examples/sudoku/server_basic.cc
            
              const int kCells = 81;
            
              void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp)
              {
                LOG_DEBUG << conn->name();
                size_t len = buf->readableBytes();
                while (len >= kCells + 2)
                {
                  const char* crlf = buf->findCRLF();
                  if (crlf)
                  {
                    string request(buf->peek(), crlf);
                    string id;
                    buf->retrieveUntil(crlf + 2);
                    string::iterator colon = find(request.begin(), request.end(), ':');
                    if (colon != request.end())
                    {
                      id.assign(request.begin(), colon);
                      request.erase(request.begin(), colon+1);
                    }
                    if (request.size() == implicit_cast<size_t>(kCells))
                    {
                      string result = solveSudoku(request);
                      if (id.empty())
                      {
                        conn->send(result+"\r\n");
                      }
                      else
                      {
                        conn->send(id+":"+result+"\r\n");
                      }
                    }
                    else
                    {
                      conn->send("Bad Request!\r\n");
                      conn->shutdown();
                    }
                  }
                  else
                  {
                    break;
                  }
                }
              }
            

            server_basic.cc 是一個(gè)并發(fā)服務(wù)器,可以同時(shí)服務(wù)多個(gè)客戶連接。但是它是單線程的,無法發(fā)揮多核硬件的能力。

            Sudoku 是一個(gè)計(jì)算密集型的任務(wù)(見《Muduo 設(shè)計(jì)與實(shí)現(xiàn)之一:Buffer 類的設(shè)計(jì)》中關(guān)于其性能的分析),其瓶頸在 CPU。為了讓這個(gè)單線程 server_basic 程序充分利用 CPU 資源,一個(gè)簡單的辦法是在同一臺機(jī)器上部署多個(gè) server_basic 進(jìn)程,讓每個(gè)進(jìn)程占用不同的端口,比如在一臺 8 核機(jī)器上部署 8 個(gè) server_basic 進(jìn)程,分別占用 9981、9982、……、9988 端口。這樣做其實(shí)是把難題推給了客戶端,因?yàn)榭蛻舳?s)要自己做負(fù)載均衡。再想得遠(yuǎn)一點(diǎn),在 8 個(gè) server_basic 前面部署一個(gè) load balancer?似乎小題大做了。

            能不能在一個(gè)端口上提供服務(wù),并且又能發(fā)揮多核處理器的計(jì)算能力呢?當(dāng)然可以,辦法不止一種。

            常見的并發(fā)網(wǎng)絡(luò)服務(wù)程序設(shè)計(jì)方案

            W. Richard Stevens 的 UNP2e 第 27 章 Client-Server Design Alternatives 介紹了十來種當(dāng)時(shí)(90 年代末)流行的編寫并發(fā)網(wǎng)絡(luò)程序的方案。UNP3e 第 30 章,內(nèi)容未變,還是這幾種。以下簡稱 UNP CSDA 方案。UNP 這本書主要講解阻塞式網(wǎng)絡(luò)編程,在非阻塞方面著墨不多,僅有一章。正確使用 non-blocking IO 需要考慮的問題很多,不適宜直接調(diào)用 Sockets API,而需要一個(gè)功能完善的網(wǎng)絡(luò)庫支撐。

            隨著 2000 年前后第一次互聯(lián)網(wǎng)浪潮的興起,業(yè)界對高并發(fā) http 服務(wù)器的強(qiáng)烈需求大大推動了這一領(lǐng)域的研究,目前高性能 httpd 普遍采用的是單線程 reactor 方式。另外一個(gè)說法是 IBM Lotus 使用 TCP 長連接協(xié)議,而把 Lotus 服務(wù)端移植到 Linux 的過程中 IBM 的工程師們大大提高了 Linux 內(nèi)核在處理并發(fā)連接方面的可伸縮性,因?yàn)橐粋€(gè)公司可能有上萬人同時(shí)上線,連接到同一臺跑著 Lotus server 的 Linux 服務(wù)器。

            可伸縮網(wǎng)絡(luò)編程這個(gè)領(lǐng)域其實(shí)近十年來沒什么新東西,POSA2 已經(jīng)作了相當(dāng)全面的總結(jié),另外以下幾篇文章也值得參考。

            http://bulk.fefe.de/scalable-networking.pdf

            http://www.kegel.com/c10k.html

            http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf

            下表是陳碩總結(jié)的 10 種常見方案。其中“多連接互通”指的是如果開發(fā) chat 服務(wù),多個(gè)客戶連接之間是否能方便地交換數(shù)據(jù)(chat 也是《談一談網(wǎng)絡(luò)編程學(xué)習(xí)經(jīng)驗(yàn)》中舉的三大 TCP 網(wǎng)絡(luò)編程案例之一)。對于 echo/http/sudoku 這類“連接相互獨(dú)立”的服務(wù)程序,這個(gè)功能無足輕重,但是對于 chat 類服務(wù)至關(guān)重要。“順序性”指的是在 http/sudoku 這類請求-響應(yīng)服務(wù)中,如果客戶連接順序發(fā)送多個(gè)請求,那么計(jì)算得到的多個(gè)響應(yīng)是否按相同的順序發(fā)還給客戶(這里指的是在自然條件下,不含刻意同步)。reactor_comparison

            UNP CSDA 方案歸入 0~5。5 也是目前用得很多的單線程 reactor 方案,muduo 對此提供了很好的支持。6 和 7 其實(shí)不是實(shí)用的方案,只是作為過渡品。8 和 9 是本文重點(diǎn)介紹的方案,其實(shí)這兩個(gè)方案已經(jīng)在《多線程服務(wù)器的常用編程模型》一文中提到過,只不過當(dāng)時(shí)我還沒有寫 muduo,無法用具體的代碼示例來說明。

            在對比各方案之前,我們先看看基本的 micro benchmark 數(shù)據(jù)(前三項(xiàng)由 lmbench 測得):

            • fork()+exit(): 160us
            • pthread_create()+pthread_join(): 12us
            • context switch : 1.5us
            • sudoku resolve: 100us (根據(jù)題目難度不同,浮動范圍 20~200us)

            方案 0:這其實(shí)不是并發(fā)服務(wù)器,而是 iterative 服務(wù)器,因?yàn)樗淮沃荒芊?wù)一個(gè)客戶。代碼見 UNP figure 1.9,UNP 以此為對比其他方案的基準(zhǔn)點(diǎn)。這個(gè)方案不適合長連接,到是很適合 daytime 這種 write-only 服務(wù)。

            方案 1:這是傳統(tǒng)的 Unix 并發(fā)網(wǎng)絡(luò)編程方案,UNP 稱之為 child-per-client 或 fork()-per-client,另外也俗稱 process-per-connection。這種方案適合并發(fā)連接數(shù)不大的情況。至今仍有一些網(wǎng)絡(luò)服務(wù)程序用這種方式實(shí)現(xiàn),比如 PostgreSQL 和 Perforce 的服務(wù)端。這種方案適合“計(jì)算響應(yīng)的工作量遠(yuǎn)大于 fork() 的開銷”這種情況,比如數(shù)據(jù)庫服務(wù)器。這種方案適合長連接,但不太適合短連接,因?yàn)?fork() 開銷大于求解 sudoku 的用時(shí)。

            方案 2:這是傳統(tǒng)的 Java 網(wǎng)絡(luò)編程方案 thread-per-connection,在 Java 1.4 引入 NIO 之前,Java 網(wǎng)絡(luò)服務(wù)程序多采用這種方案。它的初始化開銷比方案 1 要小很多。這種方案的伸縮性受到線程數(shù)的限制,一兩百個(gè)還行,幾千個(gè)的話對操作系統(tǒng)的 scheduler 恐怕是個(gè)不小的負(fù)擔(dān)。

            方案 3:這是針對方案 1 的優(yōu)化,UNP 詳細(xì)分析了幾種變化,包括對 accept 驚群問題的考慮。

            方案 4:這是對方案 2 的優(yōu)化,UNP 詳細(xì)分析了它的幾種變化。

            以上幾種方案都是阻塞式網(wǎng)絡(luò)編程,程序(thread-of-control)通常阻塞在 read() 上,等待數(shù)據(jù)到達(dá)。但是 TCP 是個(gè)全雙工協(xié)議,同時(shí)支持 read() 和 write() 操作,當(dāng)一個(gè)線程/進(jìn)程阻塞在 read() 上,但程序又想給這個(gè) TCP 連接發(fā)數(shù)據(jù),那該怎么辦?比如說 echo client,既要從 stdin 讀,又要從網(wǎng)絡(luò)讀,當(dāng)程序正在阻塞地讀網(wǎng)絡(luò)的時(shí)候,如何處理鍵盤輸入?又比如 proxy,既要把連接 a 收到的數(shù)據(jù)發(fā)給連接 b,又要把從連接 b 收到的數(shù)據(jù)發(fā)給連接 a,那么到底讀哪個(gè)?(proxy 是《談一談網(wǎng)絡(luò)編程學(xué)習(xí)經(jīng)驗(yàn)》中舉的三大 TCP 網(wǎng)絡(luò)編程案例之一。)

            一種方法是用兩個(gè)線程/進(jìn)程,一個(gè)負(fù)責(zé)讀,一個(gè)負(fù)責(zé)寫。UNP 也在實(shí)現(xiàn) echo client 時(shí)介紹了這種方案。另外見 Python Pinhole 的代碼:http://code.activestate.com/recipes/114642/

            另一種方法是使用 IO multiplexing,也就是 select/poll/epoll/kqueue 這一系列的“多路選擇器”,讓一個(gè) thread-of-control 能處理多個(gè)連接。“IO 復(fù)用”其實(shí)復(fù)用的不是 IO 連接,而是復(fù)用線程。使用 select/poll 幾乎肯定要配合 non-blocking IO,而使用 non-blocking IO 肯定要使用應(yīng)用層 buffer,原因見《Muduo 設(shè)計(jì)與實(shí)現(xiàn)之一:Buffer 類的設(shè)計(jì)》。這就不是一件輕松的事兒了,如果每個(gè)程序都去搞一套自己的 IO multiplexing 機(jī)制(本質(zhì)是 event-driven 事件驅(qū)動),這是一種很大的浪費(fèi)。感謝 Doug Schmidt 為我們總結(jié)出了 Reactor 模式,讓 event-driven 網(wǎng)絡(luò)編程有章可循。繼而出現(xiàn)了一些通用的 reactor 框架/庫,比如 libevent、muduo、Netty、twisted、POE 等等,有了這些庫,我想基本不用去編寫阻塞式的網(wǎng)絡(luò)程序了(特殊情況除外,比如 proxy 流量限制)。

            單線程 reactor 的程序結(jié)構(gòu)是(圖片取自 Doug Lea 的演講):

            reactor_basic

            方案 5:基本的單線程 reactor 方案,即前面的 server_basic.cc 程序。本文以它作為對比其他方案的基準(zhǔn)點(diǎn)。這種方案的優(yōu)點(diǎn)是由網(wǎng)絡(luò)庫搞定數(shù)據(jù)收發(fā),程序只關(guān)心業(yè)務(wù)邏輯;缺點(diǎn)在前面已經(jīng)談了:適合 IO 密集的應(yīng)用,不太適合 CPU 密集的應(yīng)用,因?yàn)檩^難發(fā)揮多核的威力。

            方案 6:這是一個(gè)過渡方案,收到 Sudoku 請求之后,不在 reactor 線程計(jì)算,而是創(chuàng)建一個(gè)新線程去計(jì)算,以充分利用多核 CPU。這是非常初級的多線程應(yīng)用,因?yàn)樗鼮槊總€(gè)請求(而不是每個(gè)連接)創(chuàng)建了一個(gè)新線程。這個(gè)開銷可以用線程池來避免,即方案 8。這個(gè)方案還有一個(gè)特點(diǎn)是 out-of-order,即同時(shí)創(chuàng)建多個(gè)線程去計(jì)算同一個(gè)連接上收到的多個(gè)請求,那么算出結(jié)果的次序是不確定的,可能第 2 個(gè) Sudoku 比較簡單,比第 1 個(gè)先算出結(jié)果。這也是為什么我們在一開始設(shè)計(jì)協(xié)議的時(shí)候使用了 id,以便客戶端區(qū)分 response 對應(yīng)的是哪個(gè) request。

            方案 7:為了讓返回結(jié)果的順序確定,我們可以為每個(gè)連接創(chuàng)建一個(gè)計(jì)算線程,每個(gè)連接上的請求固定發(fā)給同一個(gè)線程去算,先到先得。這也是一個(gè)過渡方案,因?yàn)椴l(fā)連接數(shù)受限于線程數(shù)目,這個(gè)方案或許還不如直接使用阻塞 IO 的 thread-per-connection 方案2。方案 7 與方案 6 的另外一個(gè)區(qū)別是一個(gè) client 的最大 CPU 占用率,在方案 6 中,一個(gè) connection 上發(fā)來的一長串突發(fā)請求(burst requests) 可以占滿全部 8 個(gè) core;而在方案 7 中,由于每個(gè)連接上的請求固定由同一個(gè)線程處理,那么它最多占用 12.5% 的 CPU 資源。這兩種方案各有優(yōu)劣,取決于應(yīng)用場景的需要,到底是公平性重要還是突發(fā)性能重要。這個(gè)區(qū)別在方案 8 和方案 9 中同樣存在,需要根據(jù)應(yīng)用來取舍。

            方案 8:為了彌補(bǔ)方案 6 中為每個(gè)請求創(chuàng)建線程的缺陷,我們使用固定大小線程池,程序結(jié)構(gòu)如下圖。全部的 IO 工作都在一個(gè) reactor 線程完成,而計(jì)算任務(wù)交給 thread pool。如果計(jì)算任務(wù)彼此獨(dú)立,而且 IO 的壓力不大,那么這種方案是非常適用的。Sudoku Solver 正好符合。代碼見:http://code.google.com/p/muduo/source/browse/trunk/examples/sudoku/server_threadpool.cc 后文給出了它與方案 9 的區(qū)別。

            reactor_threadpool

            如果 IO 的壓力比較大,一個(gè) reactor 忙不過來,可以試試 multiple reactors 的方案 9。

            方案 9:這是 muduo 內(nèi)置的多線程方案,也是 Netty 內(nèi)置的多線程方案。這種方案的特點(diǎn)是 one loop per thread,有一個(gè) main reactor 負(fù)責(zé) accept 連接,然后把連接掛在某個(gè) sub reactor 中(muduo 采用 round-robin 的方式來選擇 sub reactor),這樣該連接的所有操作都在那個(gè) sub reactor 所處的線程中完成。多個(gè)連接可能被分派到多個(gè)線程中,以充分利用 CPU。Muduo 采用的是固定大小的 reactor pool,池子的大小通常根據(jù) CPU 核數(shù)確定,也就是說線程數(shù)是固定的,這樣程序的總體處理能力不會隨連接數(shù)增加而下降。另外,由于一個(gè)連接完全由一個(gè)線程管理,那么請求的順序性有保證,突發(fā)請求也不會占滿全部 8 個(gè)核(如果需要優(yōu)化突發(fā)請求,可以考慮方案 10)。這種方案把 IO 分派給多個(gè)線程,防止出現(xiàn)一個(gè) reactor 的處理能力飽和。與方案 8 的線程池相比,方案 9 減少了進(jìn)出 thread pool 的兩次上下文切換。我認(rèn)為這是一個(gè)適應(yīng)性很強(qiáng)的多線程 IO 模型,因此把它作為 muduo 的默認(rèn)線程模型。

            reactor_multiple

            代碼見:http://code.google.com/p/muduo/source/browse/trunk/examples/sudoku/server_multiloop.cc

            server_multiloop.cc 與 server_basic.cc 的區(qū)別很小,關(guān)鍵只有一行代碼:server_.setThreadNum(numThreads);

            $ diff server_basic.cc server_multiloop.cc -up
            --- server_basic.cc     2011-06-15 13:40:59.000000000 +0800
            +++ server_multiloop.cc 2011-06-15 13:39:53.000000000 +0800
            @@ -21,19 +21,22 @@ using namespace muduo::net;
             class SudokuServer
             {
              public:
            -  SudokuServer(EventLoop* loop, const InetAddress& listenAddr)
            +  SudokuServer(EventLoop* loop, const InetAddress& listenAddr, int numThreads)
                 : loop_(loop),
                   server_(loop, listenAddr, "SudokuServer"),
            +      numThreads_(numThreads),
                   startTime_(Timestamp::now())
               {
                 server_.setConnectionCallback(
                     boost::bind(&SudokuServer::onConnection, this, _1));
                 server_.setMessageCallback(
                     boost::bind(&SudokuServer::onMessage, this, _1, _2, _3));
            +    server_.setThreadNum(numThreads);
               }
            
            

            方案 8 使用 thread pool 的代碼與使用多 reactors 的方案 9 相比變化不大,只是把原來 onMessage() 中涉及計(jì)算和發(fā)回響應(yīng)的部分抽出來做成一個(gè)函數(shù),然后交給 ThreadPool 去計(jì)算。記住方案 8 有 out-of-order 的可能,客戶端要根據(jù) id 來匹配響應(yīng)。

            $ diff server_multiloop.cc server_threadpool.cc -up
            --- server_multiloop.cc 2011-06-15 13:39:53.000000000 +0800
            +++ server_threadpool.cc        2011-06-15 14:07:52.000000000 +0800
            @@ -31,12 +32,12 @@ class SudokuServer
                     boost::bind(&SudokuServer::onConnection, this, _1));
                 server_.setMessageCallback(
                     boost::bind(&SudokuServer::onMessage, this, _1, _2, _3));
            -    server_.setThreadNum(numThreads);
               }
            
               void start()
               {
                 LOG_INFO << "starting " << numThreads_ << " threads.";
            +    threadPool_.start(numThreads_);
                 server_.start();
               }
            
            @@ -68,15 +69,7 @@ class SudokuServer
                     }
                     if (request.size() == implicit_cast<size_t>(kCells))
                     {
            -          string result = solveSudoku(request);
            -          if (id.empty())
            -          {
            -            conn->send(result+"\r\n");
            -          }
            -          else
            -          {
            -            conn->send(id+":"+result+"\r\n");
            -          }
            +          threadPool_.run(boost::bind(solve, conn, request, id));
                     }
                     else
                     {
            @@ -91,8 +84,23 @@ class SudokuServer
                 }
               }
            
            +  static void solve(const TcpConnectionPtr& conn, const string& request, const string& id)
            +  {
            +    LOG_DEBUG << conn->name();
            +    string result = solveSudoku(request);
            +    if (id.empty())
            +    {
            +      conn->send(result+"\r\n");
            +    }
            +    else
            +    {
            +      conn->send(id+":"+result+"\r\n");
            +    }
            +  }
            +
               EventLoop* loop_;
               TcpServer server_;
            +  ThreadPool threadPool_;
               int numThreads_;
               Timestamp startTime_;
             };
            

            完整代碼見:http://code.google.com/p/muduo/source/browse/trunk/examples/sudoku/server_threadpool.cc

            方案 10:把方案 8 和方案 9 混合,既使用多個(gè) reactors 來處理 IO,又使用線程池來處理計(jì)算。這種方案適合既有突發(fā) IO (利用多線程處理多個(gè)連接上的 IO),又有突發(fā)計(jì)算的應(yīng)用(利用線程池把一個(gè)連接上的計(jì)算任務(wù)分配給多個(gè)線程去做)。

            reactor_hybrid

            這種其實(shí)方案看起來復(fù)雜,其實(shí)寫起來很簡單,只要把方案 8 的代碼加一行 server_.setThreadNum(numThreads); 就行,這里就不舉例了。

            結(jié)語

            我在《多線程服務(wù)器的常用編程模型》一文中說

            總結(jié)起來,我推薦的多線程服務(wù)端編程模式為:event loop per thread + thread pool。

            • event loop 用作 non-blocking IO 和定時(shí)器。
            • thread pool 用來做計(jì)算,具體可以是任務(wù)隊(duì)列或消費(fèi)者-生產(chǎn)者隊(duì)列。

            當(dāng)時(shí)(2010年2月)我還說“以這種方式寫服務(wù)器程序,需要一個(gè)優(yōu)質(zhì)的基于 Reactor 模式的網(wǎng)絡(luò)庫來支撐,我只用過in-house的產(chǎn)品,無從比較并推薦市面上常見的 C++ 網(wǎng)絡(luò)庫,抱歉。

            現(xiàn)在有了 muduo 網(wǎng)絡(luò)庫,我終于能夠用具體的代碼示例把思想完整地表達(dá)出來。

            posted on 2011-06-16 12:58 陳碩 閱讀(5909) 評論(2)  編輯 收藏 引用 所屬分類: muduo

            評論

            # re: Muduo 多線程模型:一個(gè) Sudoku 服務(wù)器演變 2011-06-16 21:18 Skill

            陳碩 你的文章欣賞價(jià)值超過了很多書籍 很敬仰你  回復(fù)  更多評論   

            # re: Muduo 多線程模型:一個(gè) Sudoku 服務(wù)器演變 2011-06-17 23:45 haskell

            出書吧,我買:)  回復(fù)  更多評論   

            <2011年2月>
            303112345
            6789101112
            13141516171819
            20212223242526
            272812345
            6789101112

            導(dǎo)航

            統(tǒng)計(jì)

            常用鏈接

            隨筆分類

            隨筆檔案

            相冊

            搜索

            最新評論

            閱讀排行榜

            評論排行榜

            久久精品亚洲精品国产欧美| 欧美黑人激情性久久| 国产精品日韩欧美久久综合| 国产午夜精品理论片久久| 久久婷婷五月综合97色直播| 伊人色综合久久天天人手人婷| 久久久久人妻一区精品色| 岛国搬运www久久| 久久久一本精品99久久精品88| 99久久婷婷国产综合精品草原| 久久人人爽人人爽人人片av麻烦 | 国产香蕉久久精品综合网| 久久久久国产精品熟女影院| 久久中文字幕视频、最近更新| 久久人人爽爽爽人久久久| 午夜精品久久久久久久无码| 天天久久狠狠色综合| 日日躁夜夜躁狠狠久久AV| 久久午夜免费视频| 久久精品一区二区影院| 欧美久久精品一级c片片| 亚洲精品tv久久久久久久久| 亚洲精品成人网久久久久久| 国产精品久久久天天影视| 麻豆AV一区二区三区久久| 日本WV一本一道久久香蕉| 人人狠狠综合久久亚洲| 国产亚洲精午夜久久久久久 | 欧美亚洲另类久久综合婷婷 | 国产99久久久国产精品小说| 精品国产婷婷久久久| 青青青青久久精品国产| 久久er热视频在这里精品| 狠狠色噜噜狠狠狠狠狠色综合久久| 亚洲AV无码1区2区久久| 久久精品国产99久久无毒不卡| 99精品国产综合久久久久五月天| 国内精品久久久久影院老司| 少妇久久久久久久久久| 亚洲精品乱码久久久久久蜜桃不卡 | 国产精品久久久久一区二区三区|