1. 程式人生 > >開源網路庫boost.asio,libevent,mongoose學習記錄以及多執行緒模式的實現

開源網路庫boost.asio,libevent,mongoose學習記錄以及多執行緒模式的實現

目錄

IO操作:

poll:

總結:

   首先說明一下幾個基礎概念:     

  • IO操作:

IO操作包括兩個部分:

      等待資料準備好:對於一個套介面上的操作,這一步驟關係到資料從網路到達,並將其複製到核心的某個緩衝區。

      將資料從核心緩衝區複製到程序緩衝區。

同步IO和非同步IO:

同步IO導致請求程序阻塞,直到IO操作完成;

非同步IO不導致請求程序阻塞。

  •  IO多路複用(select,poll,epoll)

             使用c++進行網路開發,socket幾乎是一切技術的基石。最簡單的socket套接字用法就是listen後呼叫accept阻塞等待客戶端的連線,每當有一個連線到來的時候,建立子套接字對連線進行處理,因為網路傳輸中最影響效能的是IO的讀寫,這樣的話如果短時間內有多個連線請求,socket只能一個一個的去處理。前一個IO進行讀寫的時候,因為程序阻塞,accept是沒法準備接收下一個連線的。

               這種情況有個簡單的解決方式,就是accept返回後,在新的執行緒中進行資料收發,這樣主執行緒裡面的accept可以繼續接收下一個客戶端連線請求。這種方式可以同時處理多個IO,但是會產生建立銷燬程序的開銷,特別是在短任務的情況下開銷會更大。

              IO多路複用可以在單個執行緒內監聽多個socket連線請求,此模型用到select和poll函式,這兩個函式也會使程序阻塞,select先阻塞,有活動套接字才返回,但是和阻塞I/O不同的是,這兩個函式可以同時阻塞多個I/O操作,而且可以同時對多個讀操作,多個寫操作的I/O函式進行檢測,直到有資料可讀或可寫(就是監聽多個socket)。select被呼叫後,程序會被阻塞,核心監視所有select負責的socket,當有任何一個socket的資料準備好了,select就會返回套接字可讀,我們就可以呼叫recvfrom處理資料。正因為阻塞I/O只能阻塞一個I/O操作,而I/O複用模型能夠阻塞多個I/O操作,所以才叫做多路複用。

              select,poll,epoll都是IO多路複用的機制。I/O多路複用就通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程式進行相應的讀寫操作。但select,poll,epoll本質上都是同步I/O,因為他們都需要在讀寫事件就緒後自己負責進行讀寫,也就是說這個讀寫過程是阻塞的。

select:

select系統呼叫的目的是:在一段指定時間內,監聽使用者感興趣的檔案描述符上的可讀、可寫和異常事件。poll和select應該被歸類為這樣的系統 呼叫:它們可以阻塞地同時探測一組連線請求,直至某一個裝置觸發了事件或者超過了指定的等待時間——也就是說它們的職責不是做IO,而是幫助呼叫者尋找當前就緒的裝置。 

IO多路複用模型是建立在核心提供的多路分離函式select基礎之上的,使用select函式可以避免同步非阻塞IO模型中輪詢等待的問題。

https://images0.cnblogs.com/blog/405877/201411/142332187256396.png

select的優點:可以在一個執行緒上同時監聽多個連線請求。

select的幾大缺點:

(1)每次呼叫select,都需要把fd集合(檔案描述符)從使用者態拷貝到核心態,這個開銷在fd很多時會很大

(2)同時每次呼叫select都需要在核心遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大

(3)select支援的檔案描述符數量太小了,預設是1024

poll:

  poll的實現和select非常相似,只是描述fd集合的方式不同,poll使用pollfd結構而不是select的fd_set結構,poll支援的檔案描述符數量沒有限制,其他的都差不多。

epoll:

  epoll既然是對select和poll的改進,就應該能避免上述的三個缺點。那epoll都是怎麼解決的呢?在此之前,我們先看一下epoll和select和poll的呼叫介面上的不同,select和poll都只提供了一個函式——select或者poll函式。而epoll提供了三個函式,epoll_create,epoll_ctl和epoll_wait,epoll_create是建立一個epoll控制代碼;epoll_ctl是註冊要監聽的事件型別;epoll_wait則是等待事件的產生。

  對於第一個缺點,epoll的解決方案在epoll_ctl函式中。每次註冊新的事件到epoll控制代碼中時(在epoll_ctl中指定EPOLL_CTL_ADD),會把所有的fd拷貝進核心,而不是在epoll_wait的時候重複拷貝。epoll保證了每個fd在整個過程中只會拷貝一次。

  對於第二個缺點,epoll的解決方案不像select或poll一樣每次都把current輪流加入fd對應的裝置等待佇列中,而只在epoll_ctl時把current掛一遍(這一遍必不可少)併為每個fd指定一個回撥函式,當裝置就緒,喚醒等待佇列上的等待者時,就會呼叫這個回撥函式,而這個回撥函式會把就緒的fd加入一個就緒連結串列)。epoll_wait的工作實際上就是在這個就緒連結串列中檢視有沒有就緒的fd(利用schedule_timeout()實現睡一會,判斷一會的效果,和select中的實現是類似的)。

  對於第三個缺點,epoll沒有這個限制,它所支援的FD上限是最大可以開啟檔案的數目,這個數字一般遠大於2048,舉個例子,在1GB記憶體的機器上大約是10萬左右,具體數目在linux上可以cat /proc/sys/fs/file-max察看,一般來說這個數目和系統記憶體關係很大。

select,poll實現需要自己不斷輪詢所有fd集合,直到裝置就緒,期間可能要睡眠和喚醒多次交替。而epoll其實也需要呼叫epoll_wait不斷輪詢就緒連結串列,期間也可能多次睡眠和喚醒交替,但是它是裝置就緒時,呼叫回撥函式,把就緒fd放入就緒連結串列中,並喚醒在epoll_wait中進入睡眠的程序。雖然都要睡眠和交替,但是select和poll在“醒著”的時候要遍歷整個fd集合,而epoll在“醒著”的時候只要判斷一下就緒連結串列是否為空就行了,這節省了大量的CPU時間。這就是回撥機制帶來的效能提升。

select,poll每次呼叫都要把fd集合從使用者態往核心態拷貝一次,並且要把current往裝置等待佇列中掛一次,而epoll只要一次拷貝,而且把current往等待佇列上掛也只掛一次(在epoll_wait的開始,注意這裡的等待佇列並不是裝置等待佇列,只是一個epoll內部定義的等待佇列)。這也能節省不少的開銷。

  • 非同步IO(iocp,epoll)

        epoll屬於IO多路複用,它只是模擬實現了非同步IO的功能。  “真正”的非同步IO需要作業系統更強的支援。在IO多路複用模型中,事件迴圈將檔案控制代碼的狀態事件通知給使用者執行緒,由使用者執行緒自行讀取資料、處理資料。而在非同步IO模型中,當用戶執行緒收到通知時,資料已經被核心讀取完畢,並放在了使用者執行緒指定的緩衝區內,核心在IO完成後通知使用者執行緒直接使用即可。

          IOCP全稱 IO完成埠。它是一種WIN32的網路I/O模型,既包括了網路連線部分,也負責了部分的I/O操作功能,用於方便我們控制有併發性的網路I/O操作。它有如下特點:
1:它是一個WIN32核心物件,所以無法運行於linux
2:它自己負責維護了工作執行緒池,同時也負責了I/O通道的記憶體池。
3:它自己實現了執行緒的管理以及I/O請求通知,最小化的做到了執行緒的上下文切換。
4:它自己實現了執行緒的優化排程,提高了CPU和記憶體緩衝的使用率。

           真正意義上的非同步IO嚴格的來說只有IOCP,但是epoll也模擬實現了非同步IO的功能。

        epoll 因為採用 mmap的機制, 使得 核心socket buffer和 使用者空間的 buffer共享, 從而省去了 socket data copy, 這也意味著, 當epoll 回撥上層的 callback函式來處理 socket 資料時, 資料已經從核心層 "自動" 到了使用者空間, 雖然和 用poll 一樣, 使用者層的程式碼還必須要呼叫 read/write, 但這個函式內部實現所觸發的深度不同了。

        poll 時, poll通知使用者空間的Appliation時, 資料還在核心空間, 所以Appliation呼叫 read API 時, 內部會做 copy socket data from kenel space to user space。

       而用 epoll 時, epoll 通知使用者空間的Appliation時, 資料已經在使用者空間, 所以 Appliation呼叫 read API 時, 只是讀取使用者空間的 buffer, 沒有 kernal space和 user space的switch了。

IOCP和Epoll之間的異同。
異:
1:IOCP是WINDOWS系統下使用。Epoll是Linux系統下使用。
2:IOCP是IO操作完畢之後,通過Get函式獲得一個完成的事件通知。
Epoll是當你希望進行一個IO操作時,向Epoll查詢是否可讀或者可寫,若處於可讀或可寫狀態後,Epoll會通過epoll_wait進行通知。
3:IOCP封裝了非同步的訊息事件的通知機制,同時封裝了部分IO操作。但Epoll僅僅封裝了一個非同步事件的通知機制,並不負責IO讀寫操作,但是因為mmap機制,epoll其實已經省去了IO操作的第二部分(將資料從核心緩衝區複製到程序緩衝區)。
4: 基於上面的描述,我們可以知道Epoll不負責IO操作,所以它只告訴你當前可讀可寫了,並且將協議讀寫緩衝填充,由使用者去讀寫控制,此時我們可以做出額 外的許多操作。IOCP則直接將IO通道里的讀寫操作都做完了才通知使用者,當IO通道里發生了堵塞等狀況我們是無法控制的。

同:
1:它們都是非同步的事件驅動的網路模型。
2:它們都可以向底層進行指標資料傳遞,當返回事件時,除可通知事件型別外,還可以通知事件相關資料(通知到來時IO已經完全完成)。

Libevent

libevent是一個輕量級的基於事件驅動的高效能的開源網路庫,並且支援多個平臺,對多個平臺的I/O複用技術進行了封裝。在linux下面集成了poll,epoll;在window下面集成了select,舊版本沒有整合IOCP,所以在window上面 libevent的效能並不優秀,新版本的libevent也集成了IOCP,但是隻作為網路開發庫的話,libevent的綜合評價還是不如boost.asio。

對於網路通訊Libevent和boost.asio功能相近,但是asio綜合性能更好,而且整合到了boost裡面,只需要引入標頭檔案即可使用。所以需要開發高效能web服務的時候,推薦使用asio,在這裡就不再臃述libevent。

Boost.asio

Boost.Asio是利用當代C++的先進方法,跨平臺,非同步I/O模型的C++網路庫,Windows下使用IOCP,Linux下使用epoll。下面是一個asio使用多執行緒非同步IO的網路伺服器demo。


#include "stdafx.h"
#ifdef WIN32
#define _WIN32_WINNT 0x0501
#include <stdio.h>
#endif
#include <iostream> 
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread/thread.hpp>
#include <boost/enable_shared_from_this.hpp>
using namespace boost::asio;
using namespace boost::posix_time;
io_service service;

class talk_to_client;
typedef boost::shared_ptr<talk_to_client> client_ptr;
typedef std::vector<client_ptr> array;
array clients;

#define MEM_FN(x)       boost::bind(&self_type::x, shared_from_this())
#define MEM_FN1(x,y)    boost::bind(&self_type::x, shared_from_this(),y)
#define MEM_FN2(x,y,z)  boost::bind(&self_type::x, shared_from_this(),y,z)

void update_clients_changed();

//業務類,對每個事件進行io讀寫及處理
class talk_to_client : public boost::enable_shared_from_this<talk_to_client>
	, boost::noncopyable {
		typedef talk_to_client self_type;
		talk_to_client() : sock_(service), started_(false),
			timer_(service), clients_changed_(false) {
		}
public:
	typedef boost::system::error_code error_code;
	typedef boost::shared_ptr<talk_to_client> ptr;

	void start() {
		started_ = true;
		clients.push_back(shared_from_this());
		last_ping = boost::posix_time::microsec_clock::local_time();
		do_read();
	}
	static ptr new_() {
		ptr new_(new talk_to_client);
		return new_;
	}
	void stop() {
		sock_.close();
	}
	
	ip::tcp::socket & sock() { return sock_; }
	std::string username() const { return username_; }
	void set_clients_changed() { clients_changed_ = true; }
private:
	void on_read(const error_code & err, size_t bytes) {
		if (err) stop();
		std::string msg(read_buffer_, bytes);
		if (msg.find("GET") == 0) on_get(msg);
		else if(msg.find("POST") == 0) on_post(msg);
	}

	void on_get(const std::string & msg) {
		std::istringstream in(msg);
		in >> username_ >> username_;
		std::cout << username_ << " logged in" << std::endl;
	

		std::string str_out = "HTTP/1.1 200 OK\r\n"
			"Access-Control-Allow-Origin: *\r\n"
			"Access-Control-Allow-Methods: *\r\n"
			"Content-Type: application/json;charset=UTF-8\r\n"
			"Server: wz simple httpd 1.0\r\n"
			"Connection: close\r\n"
			"\r\n";

		
		str_out = str_out + "{\"result\":\"asio get ok\"}";
		
		do_write(str_out);
	
	}

	

	void on_post(const std::string & msg) {
		std::istringstream in(msg);
		in >> username_ >> username_;
		std::cout << username_ << " logged in" << std::endl;
		

		std::string str_out = "HTTP/1.1 200 OK\r\n"
			"Access-Control-Allow-Origin: *\r\n"
			"Access-Control-Allow-Methods: *\r\n"
			"Content-Type: application/json;charset=UTF-8\r\n"
			"Server: wz simple httpd 1.0\r\n"
			"Connection: close\r\n"
			"\r\n";
	
		str_out = str_out + "{\"result\":\"asio post ok\"}";
		
		//Sleep(5000);
		do_write(str_out);
	
	}

		

	void on_write(const error_code & err, size_t bytes) {
		stop();	
	}
	void do_read() {

		sock_.async_read_some(buffer(read_buffer_), MEM_FN2(on_read, _1, _2));
	}
	void do_write(const std::string & msg) {
		std::copy(msg.begin(), msg.end(), write_buffer_);
		sock_.async_write_some(buffer(write_buffer_, msg.size()),
			MEM_FN2(on_write, _1, _2));
	}
	
	
private:
	ip::tcp::socket sock_;
	enum { max_msg = 1024 };
	char read_buffer_[max_msg];
	char write_buffer_[max_msg];
	bool started_;
	std::string username_;
	boost::posix_time::ptime last_ping;
	bool clients_changed_;
};



ip::tcp::acceptor acceptor(service, ip::tcp::endpoint(ip::tcp::v4(), 82));//監聽埠號

void handle_accept(talk_to_client::ptr client, const boost::system::error_code & err);

void ConnFunc(talk_to_client::ptr client)
{
	client->start();
	return;
}

void handle_accept(talk_to_client::ptr client, const boost::system::error_code & err) {


	client->start();

	talk_to_client::ptr new_client = talk_to_client::new_();
	acceptor.async_accept(new_client->sock(), boost::bind(handle_accept, new_client, _1));	
	
}


int _tmain(int argc, _TCHAR* argv[])
{
	talk_to_client::ptr client = talk_to_client::new_();
	acceptor.async_accept(client->sock(), boost::bind(handle_accept, client, _1));

	boost::thread_group thrds;
	for (int i = 0; i < 4; ++i)//建立了4個執行緒監聽io,每個執行緒都可以監聽多個連線請求。使用多執行緒主要是為了防止業務處理的時候阻塞,如果只是單純的轉發伺服器,單執行緒效率更高。
	{
		thrds.create_thread(boost::bind(&boost::asio::io_service::run, &service));
	}

	thrds.join_all();
	//service.stop();
	
	return 0;
}

一般來說,高效能web伺服器的io和業務處理都是分離的,伺服器開銷主要在io上。因為asio已經實現了非同步io,所以如果只是作為轉發伺服器,只使用一個執行緒處理即可(多執行緒有執行緒切換的開銷)。比如nginx就是使用單執行緒非同步io的伺服器。

但是如果io和業務處理沒有分離,比如上例中處理post的時候 sleep了5秒(假設業務處理佔用了5秒的時間),如果使用單執行緒那就會阻塞客戶端新的請求(其實請求不會阻塞,只是asio的事件回撥函式被阻塞了)。在這種情況下就需要使用如上例的多執行緒處理。

關於boost.asio介紹不錯的兩個連結:

Mongoose

mongoose是一個超輕量級的網路庫,只包含兩個檔案 mongoose.c 和 mongoose.h兩個檔案,引入到工程中即可使用,無第三方依賴且跨平臺。底層只使用了select,所以效能上當然沒法和asio,libevent比,但是開發一般的小型的PC端web伺服器足夠了,mongoose也是我最終選定的伺服器開發解決方案。

但是mongoose使用多執行緒是有些問題的,下面是使用 6.10版本實現的多執行緒web伺服器demo:

// httpserver.cpp : 定義控制檯應用程式的入口點。
//
//#define  _SLIST_HEADER_
#include "stdafx.h"
#include <string>  
#include <boost/thread/thread.hpp>
#include "mongoose.h"


boost::mutex lock;

static bool stop_webserver = false;

#define MAX_THREADS 10//最大執行緒數

static const char *s_http_port = "82";  


static void ev_handler(struct mg_connection *nc, int ev, void *p)
{
	if(NULL == nc || NULL==p)
	{
		return;
	}
	

	switch (ev) 
	{
		case MG_EV_ACCEPT: 
			{
				char addr[32];
				mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr),
					MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);
				printf("Connection %p from %s\n", nc, addr);
				break;
			}
		case MG_EV_HTTP_REQUEST: 
			{
				char addr[32];
				struct http_message *hm = (struct http_message *) p;
				const char* uri = hm->uri.p;
				mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr),
					MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);
				printf("HTTP request from %s: %.*s %.*s\n", addr, (int) hm->method.len,
					hm->method.p, (int) hm->uri.len, hm->uri.p);
				
				std::string method = std::string(hm->method.p, hm->method.len);
				

				std::string str_out = "HTTP/1.1 200 OK\r\n"
					"Access-Control-Allow-Origin: *\r\n"
					"Access-Control-Allow-Methods: *\r\n"
					"Content-Type: application/json;charset=UTF-8\r\n"
					"Server: wz simple httpd 1.0\r\n"
					"Connection: close\r\n"
					"\r\n";
				if("POST"==method)
				{
					Sleep(5000);
					str_out = str_out + std::string("{\"result\":\"mongoose post ok\"}");
				}
				else
				{
					str_out = str_out + std::string("{\"result\":\"mongoose get ok\"}");
				}
				mg_printf(nc, str_out.c_str());
				nc->flags |= MG_F_SEND_AND_CLOSE;

				
				break;
			}
	}
	return;
}

void* process_proc(void* mgr)
{
	while(!stop_webserver)
	{	
		mg_mgr_poll((struct mg_mgr*)mgr, 1000);  	
	}
	mg_mgr_free((struct mg_mgr*)mgr); 
	return NULL;
}



int TestHttpServer(void) {  
	
	struct mg_mgr mgr;  
	struct mg_connection *nc;

	stop_webserver = false;

	mg_mgr_init(&mgr, NULL);  
	nc = mg_bind(&mgr, s_http_port, ev_handler); 
	mg_set_protocol_http_websocket(nc);  
	
	for(int i = 0; i < MAX_THREADS; i++)
	{		
		mg_start_thread(process_proc,&mgr);	
	}

	
	return 0;  
}  


int _tmain(int argc, _TCHAR* argv[])
{

	int result = TestHttpServer();

    while(1)
	{
		Sleep(1000);
	}

	return result;
}

mongoose6.10版本開啟多執行緒的方法在官網和百度上都沒有找到明確的介紹,國內網站上能找到的都是很老版本的demo,公司沒有條件翻牆,不清楚谷歌上有沒有靠譜的解決方案。我捋了一下原始碼,感覺只需要使用 mg_start_thread 複製多個執行緒即可(如上例)。但是在模擬高併發請求的時候,核心事件觸發的時候有機率會崩潰。我除錯了半天只能確定是事件觸發的時候使用了已經釋放的nc物件,這種情況大部分是指標物件釋放的時候指標沒置NULL造成的,但是我經過各種嘗試始終沒能修復。感覺6.10版本的程式碼應該是沒有bug的,或許是我的使用方式不對,希望知道原因的朋友能夠留言賜教一下。

6.10版本學習資源實在太少,幾乎只有原始碼和部分簡單的單執行緒demo,所以我降低了版本,使用了mongoose5.2版本實現多執行緒的功能。但是也有一些問題,5.2的版本可能是編譯環境太老了,我下載下來的時候是不能編譯通過的,修改了原始碼最終能正常使用,高併發測試功能也比較穩定,沒有像6.10版本那樣崩潰。但是6.10和5.2的原始碼相比變動實在是太大了,除了底層還是使用select感覺大部分功能和函式都已經變化了。比如多執行緒的實現,5.2是建立了一個監聽執行緒,多個工作執行緒進行處理,雖然也能實現併發,但是設計方式確實有點太陳舊(本質上就是多執行緒阻塞socket加select),demo如下:

// httpserver.cpp : 定義控制檯應用程式的入口點。
//
//#define  _SLIST_HEADER_
#include "stdafx.h"
#include <string>  
#include <boost/thread/thread.hpp>
#include "./utils/mongoose.h"
#include <windows.h>
#define MAX_THREADS 10
static struct mg_server *server[MAX_THREADS];
 
static bool stop_webserver = false;
static const char* port = "82";
static const char* local_fullpath = "/Users/alex/";
 
static int request_handler(struct mg_connection *conn)
{
	 std::string req_str = std::string(conn->request_method);
	 
	

	 std::string str_out = "HTTP/1.1 200 OK\r\n"
		 "Access-Control-Allow-Origin: *\r\n"
		 "Access-Control-Allow-Methods: *\r\n"
		 "Content-Type: application/json;charset=UTF-8\r\n"
		 "Server: wz simple httpd 1.0\r\n"
		 "Connection: close\r\n";
	std::string str_out2;
	 if("POST" == req_str)
	 {
		str_out2 = "{\"result\":\"xia post ok\"}";
		Sleep(3000);
	 }
	 else
	 {
		str_out2 = "{\"result\":\"xia get ok\"}";
	 }

	 mg_printf(conn,str_out.c_str());
	 mg_send_data(conn,str_out2.c_str(),str_out2.length());
	
	 return 1;


}
 
void* process_proc(void* p_server)
{
	while(!stop_webserver)
	{
		mg_poll_server((struct mg_server*)p_server, 500);
	}
	return NULL;
}
 
bool start_webserver(void) {
	stop_webserver = false;
	for(int i = 0; i < MAX_THREADS; i++)
	{
		server[i] = mg_create_server(NULL);
		if(i == 0)
		{
			mg_set_option(server[i], "listening_port", port);
		}
		else
		{
			mg_set_listening_socket(server[i], mg_get_listening_socket(server[0]));
		}
		mg_set_option(server[i], "document_root", local_fullpath);
		mg_set_request_handler(server[i], request_handler);
		mg_start_thread(process_proc, server[i]);
	}
 
	return true;
}
 
void stop_simple_webserver()
{
	stop_webserver = true;
	for(int i = 0; i < MAX_THREADS; i++)
	{
		mg_destroy_server(&server[i]);
	}
}

int _tmain(int argc, _TCHAR* argv[])
{

	int result = start_webserver();

	while(1)
	{
		Sleep(1000);
	}
	return result;
}

程式碼極簡,都不用說明了。雖然5.2的這種方式和效果也算可以,可是最新版本都6.12了,始終差強人意。我還是希望能使用最新版本的mongoose實現穩定的多執行緒伺服器。

總結:

不論是強大相對複雜的boost.asio,libevent 還是輕簡的 mongoose,他們都是封裝了select,poll,epoll,iocp。

select,poll都只是IO複用,IO其實還是阻塞的。

iocp是真正意義上的非同步IO,因為它使用 win32的 完成埠 這個設計思想讓核心完全代替 使用者執行緒完成了監聽和讀寫的功能,當核心傳送訊號給使用者執行緒的時候一切事情都做完了,收到的資料都被核心放進使用者執行緒空間了,是完全沒有IO阻塞的。

epoll嚴格來說還只是IO複用,但是因為mmap機制, 使得 核心socket buffer和 使用者空間的 buffer共享,所以核心收到資料後不需要再讓使用者執行緒二次拷貝,也同樣模擬實現了非同步IO的功能。

當然IO的第一步操作 “等待資料準備好”不論使用哪種方式都是阻塞的,但這種阻塞是在核心,使用者執行緒就不用關心了。

我的理解是不論哪種技術,最最底層都是靠多執行緒實現的,單執行緒說到底都是阻塞的,因為單執行緒是不可能在一個時間片裡同時幹多件事的(鑽牛角尖)。那些強大的技術,比如epoll,iocp等是在核心裡面使用多執行緒和各種優化方案提高了效能,只是最後暴露給我們使用者態的是單執行緒。

關於mongoose6.10版本多執行緒的bug,希望有興趣的朋友多多指點一下。本篇部落格只是學習記錄,參考了多篇部落格內容,有些內容並不是原創,有些理解可也能有誤,歡迎拍磚。