前言
- 因為本科畢業設計中要做一個分布式文件系統,其中一個模塊需要實現文件IO。為了驗證我對異步IO的理解,決定造一個異步文件IO的輪子。操作系統已經給出各種異步操作的API,如重疊IO, IOCP,kqueue,select,poll,epoll等機制,而且C++也有很多跨平台的異步IO庫,如libevent,boost::asio等。我參考已有的實現來完善這個小系統的功能。
- 渣技術,渣代碼,該日志也只是作為這個工程的記錄,出現問題請各位指出。
概述
- 同步與異步:
同步:假如我想對一個文件(socket也同理)進行處理,那么一般的流程就是:
1 fstream file; 2 file.open(); 3 file.read(); 4 //do something 5 file.close();
通常情況下,當這個線程運行到read()時會被阻塞,直到文件讀取完成。
異步:
還是上面的代碼,我在read()時通過操作系統或庫提供的異步機制,告訴操作系統我想讀一個文件,數據讀完后執行某個功能;而當前線程在交代完操作系統該做什么工作之后,還可以做些別的事情(線程不必等待文件IO完成)。
- 線程池
為了避免IO阻塞線程導致程序無響應,完全可以為每一個文件操作創建一個線程,這樣就可以同時處理多個文件了。但是創建線程,切換線程,銷毀線程也是一筆資源開銷,如果想重復使用已有的線程,就可以使用線程池。作為線程池,至少要提供創建線程和提交任務的功能,復雜一點可以智能控制線程池里的線程數量,還應該具有基本的負載均衡功能。這個文件系統中就會使用線程池。Windows API中的ThreadPool就很好用,但既然是造輪子,那么為了造輪子而造輪子也沒什么關系,干脆就寫個簡單的線程池出來。
- 使用的庫
這個模塊只使用stl 和boost 兩個庫。
stl主要涉及容器和fstream。boost涉及到智能指針shared_ptr,線程同步shared_mutex,lock_guard, boost::filesystem中的path和一些文件操作,線程操作創建退出等。
- 智能指針
自古以來內存管理都是C/C++中的重頭戲,智能指針的功能就是分配出來的內存由庫管理,如果某個智能指針指向的內存,通過其他的智能指針也能訪問到(即有多個引用),那么該智能指針即時被銷毀,指向的內存也不會銷毀;只有這塊內存沒有引用,才會被庫釋放。
- boost::filesystem
這個庫提供了一些跨平台文件操作的API,如文件夾遍歷,查看屬性,刪除文件等。path類可以記錄跨平台的路徑。
- 因為水平有限,這個模塊基本不會出現跟模板有關的實現。(以后再說)
實現
AsyncStatus是異步IO中需要實現的功能。像讀,寫,放棄異步操作,錯誤處理等。
ErrorCode會出現在回調函數中,表示之前異步讀寫的結果,如正在處理,出錯,EOF等。
FS_Handle_ST:這個結構體對應一個文件路徑。在系統中每個handle都是唯一的,系統有一個map,通過handle可以找到它對應的路徑。
FS_AsyncHandle_ST:標識某個handle需要執行的任務,每個AsyncHandle都需要指定status即任務。一個handle可以有多個異步任務,但多個任務在系統中按照隊列順序執行。
1 enum QueueOperation { PUSH_BACK, PUSH_FRONT }; 2 enum AsyncStatus { NONE, APPEND_WRITE, WRITE, READ, READ_ALL, ABORT, ERROR, EXIT }; 3 enum ErrorCode { DONE, PENDING, END_OF_FILE, OPEN_FAIL, BAD_STREAM, IO_FAIL, UNKNOWN_ERROR }; 4 5 typedef uintmax_t FS_Handle; 6 typedef uintmax_t FS_AsyncHandle; 7 8 struct FS_AsyncHandle_ST { 9 FS_Handle fileHandle = 0; 10 FS_AsyncHandle asyncHandle = 0; 11 AsyncStatus status = AsyncStatus::NONE; 12 }; 13 14 struct FS_Handle_ST { 15 FS_Handle handle = 0; 16 boost::filesystem::path fullPath; 17 };
回調函數:異步操作完成之后要做什么。使用的時候把功能在派生類里實現,重載虛函數run就可以了。
1 class FileSystemIOCallback { 2 public: 3 void operator=(const FileSystemIOCallback & cb) { 4 //.. 5 } 6 7 FileSystemIOCallback(const FileSystemIOCallback & cb) { 8 //.. 9 } 10 11 FileSystemIOCallback() { 12 //... 13 } 14 15 virtual ~FileSystemIOCallback() { 16 17 } 18 19 virtual void run(const FS_AsyncHandle_ST & ast, ErrorCode e, void * data, uintmax_t count) { 20 //... 21 } 22 };
- 功能概述
fstream中有一個非常有趣的函數,readsome()方法,這個方法提出的目的是盡可能減少同步讀取的阻塞時間。該方法會讀取fstream內部緩沖區里的數據,讀取的字節數取決於fstream內部緩沖區和作為參數傳遞給它的緩沖區的大小。舉個例子,如果fstream緩沖區里只有10字節,我參數給出的緩沖區大小為100字節,不管這個文件后面還有沒有數據,它只會去讀10個字節。操作系統和函數庫可能會使用由硬件引發的中斷來讀取數據,運行原理類似,但其具體實現未知,還要進一步了解。
既然IO操作要阻塞線程很長一段時間,那么就把IO操作扔給線程或纖程,主線程只負責提交任務。