關於線程任務的一些思考
當某一個線程需要對一段代碼或者數據進行訪問的時候,為了保證不會有對個線程同時訪問這段代碼或者數據的情況,都會使用鎖來做互斥。
線程A完全訪問完代碼(下面只說代碼,數據是一樣的)的時候,線程B才可以訪問。如果線程B訪問的時候,線程A正在訪問,這個時候線程B會阻塞。太多的阻塞和鎖會讓效率下降。
仔細想一想,某個線程A訪問一段代碼到底是什麽意思?
我的理解是當前整個系統狀態下需要這段代碼進行一次執行。
再繼續想,難道我必須要求這段代碼馬上(相對於這個線程)執行嗎?完全可以把這段代碼的執行做成一個任務,丟到某個專門的線程C中執行。線程C循環查看隊列中是否有需要執行的任務,當它看到線程A發送的任務後先執行,然後再回調線程A,這個回調也可以采用線程隊列的方式,即把這個執行結果發送到線程A的隊列中。這樣各個線程只通過自己的消息隊列互相協作,大大降低了耦合性。
這種模式也有很多不足的問題,比如線程A需要馬上獲悉這段代碼的執行結果,然後下面的執行邏輯依賴於這個結果。
不過個人認為這種多線程的設計是不夠好的,總是可以有辦法來避免這樣。退一步說,如果確實存在線程A需要馬上獲取執行結果的情況,也可以處理。線程A把這個任務設置優先級很高,然後就不斷循環判斷線程C是不是執行完這個任務了。假如這個時候線程C正在執行一個很耗時的任務,甚至可以讓線程C先暫停當前任務的執行,執行完線程A的任務才去執行。這種情況不多見,主要會出現在一些實時系統中。
在我們遊戲中,主要有兩個線程。邏輯線程負責執行相關的腳本邏輯和生成渲染數據;渲染線程負責渲染和OpenGL接口調用。這兩個都是循環線程。
當需要生成一個gl紋理的時候,邏輯線程獲取相關圖像數據,然後分派一個任務到渲染線程。渲染線程循環查看消息隊列,發現有生成紋理的任務就執行,然後把結果放到邏輯線程的消息隊列裏面。
當需要渲染一組對象的時候,邏輯線程也使用相關數據生成一個任務放到渲染線程。渲染線程還可以定義優先級,決定是先渲染還是先生成紋理。
當渲染線程比較忙碌而邏輯線程比較空閑的時候,是不是一些計算可以放到邏輯線程中,畢竟玩家感受到的幀率取決於二者的最小值。
總之,這種消息隊列的做法降低了耦合,增加了靈活性和健壯性。
把線程的概念抽象到一個更高層次的任務的概念,線程對代碼的執行上升到線程需要某個任務一次執行的高度,看問題的層次高了,也會發現很多更優秀的設計。
關於線程任務的一些思考