使用C++ boost從零構建一個異步文件IO系統


前言

  • 因為本科畢業設計中要做一個分布式文件系統,其中一個模塊需要實現文件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操作扔給線程或纖程,主線程只負責提交任務。

--- 未完待續


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM