一、為什麼需要線程模型? 記得幾年前,自己寫高精度演算法時,因為需要一個線程安全的後臺(用來保存一些信息),便手動寫了一個線程本地存儲(TLS)(雖然,後來因為改了計算模型,棄用了);再後來,因為記憶體池的需要,亦手動再寫了一個線程本地存儲(TLS);很好,這樣一來同一個庫里,竟然有兩套相同的TLS;於 ...
一、為什麼需要線程模型?
記得幾年前,自己寫高精度演算法時,因為需要一個線程安全的後臺(用來保存一些信息),便手動寫了一個線程本地存儲(TLS)(雖然,後來因為改了計算模型,棄用了);再後來,因為記憶體池的需要,亦手動再寫了一個線程本地存儲(TLS);很好,這樣一來同一個庫里,竟然有兩套相同的TLS;於是,意識到了什麼地方不對。
不只是代碼重覆的問題(其實重覆的不多);更重要的是,TLS應該是線程模型本身,來提供的功能;但,很可惜,C++並沒有這樣的東西(線程模型)。(PS:我無視了系統提供的TLS...)
說了這麼多;只想說一點:為了TLS,為了性能! 當然,統一的線程模型,會有一個更大的好處:統一且簡潔的多線程介面。
二、任務模型
和Java類似,線程模型本身派生與任務模型(我的最初版本並不是);這樣有一個好處:將實例化的線程,當作任務來使用(即:並不通過自身所持有的線程,來執行任務)。在一些情形下,會有這樣的需求。
作為最根本的介面,需要足夠的簡單:
class ITask :public Interface{ public: virtual void run() = 0; public: virtual void interrupted(){} public: virtual tick_t getWaitingTime()const{ return 0; } };
最重要、也最直白的便是 run() 這個介面函數;其便是我們將要執行的任務本身(所以無需多言)。值得一提的是:interrupted() 其是一個回調函數,用來通知該任務,執行過程中被意外中斷(如拋出異常)。至於 getWaitingTime() 是為線程池準備的,如果你的線程池支持延時執行。
三、線程模型
首先,要明白兩點:什麼是線程? 它用來做什麼??
在面向對象里,線程(類)即能夠開啟另外一個線程(相對進程);而其目的無外乎,用來執行代碼。所以,其包含有兩套邏輯:開啟新線程、被執行的任務介面。所以,便如下:
class Thread :public ITask{ protected: void execute(); };
其相對於 ITask,只需要提供開啟新線程的介面:execute。調用這個函數,便讓新線程開始執行 run 所定義的任務。
當然,還遠不止如此;我們可以提供更多的控制介面:suspend、resume、interrupt(用來中斷線程),以及查詢介面:isStarted、threadID。但,有一個介面,絕不能夠被遺忘:
class Thread :public ITask{ protected: void execute(); public: void wait();// here~ };
一旦線程開始執行;其最上層(Thread的子類)必須等待,該線程停止運行後,才能夠開始析構!!而提供這樣等待的便是:wait。(有GC的語言,沒有這個問題)
四、線程本地存儲
TLS,在哪裡??對,我們依舊沒有看到其絲毫的身影。其實,就在 execute 的背後——其開啟新線程的同時,會註冊該新線程,以創建一個新的線程環境(TLS);同樣,在該線程停止時,將取消註冊,並刪除該環境。
這時,我們需要一個全局的線程運行時環境,用來管理所有線程,並存儲相應的TLS(或保存其索引)。
當然,有一個問題值得提出:TLS放在哪裡? 是全局運行時環境,還是線程本身?(操作系統,一般通過線程自身的調用棧,來存放TLS)
如果,我們真有一個統一的線程模型,二者都可以選擇。可惜,我們不可能有!主線程,不可能被我們的任何模型所約束(整個程式的第一行代碼執行前,主線程便存在了)。當然,我們可以迴避這個問題,通過完全不使用主線程(即:我們的任何邏輯,都在非主線程里執行);而且需要相當的小心,比如靜態初始化。
所以,唯一的選擇就是,放在全局的運行時環境。這時,我們所見到的大概這樣:
void Thread::execute() { GetServiceAs<ThreadService>()->login();
... }
所有的一切,都存放在 ThreadService 里;那麼,如何獲得TLS,自然也是使用:
... GetServiceAs<ThreadService>()->getLocalAs<XXType>(); ...
需要註意的一點是,我們並沒有顯示地使用線程ID,來索引;而是在介面內部,通過系統API:GetCurrentThreadId 來獲取當前線程ID。
五、其他
很多細節問題,我並沒有提及,如:interrupted(),如何使用?
是的,我所有的文章里,都沒有“細節”二字;原因有二:並非數十行代碼就能夠明瞭、我的庫並沒有開源。但,相應的,所有的設計過程及細節,都已呈現給各位;只要你不笨,並有一些興趣和耐心,便不難實現(甚至比我所講的更好)。
嗯,這次的內容,足夠的少;大概是,“累”了,寫了一些後,並沒有任何同類的人,來進行期待中的交流(主要是期待不同意見);但,沒有。所以,我將要動筆的概率,大概也將越來越低......
至此。