ThreadLoop實踐學習筆記

AndGate發表於2024-07-02

背景

在日常工作和學習原始碼過程中,經常可以看到ThreadLoop的運用,發現ThreadLoop作為一個基礎工具,在具體專案中有不同而又十分相似的實現,雖然核心的機制萬變不離其宗(IO多路複用),但面向的業務場景不同導致了不同的實踐結果,目前見過有幾種ThreadLoop的實踐,本文做一個分析記錄和知識點的總結

基礎TaskLoop:

面向Task:

Task型別包裹回撥函式和必要資料,如have_run,is_run,task_tag等,TaskLoop負責執行Task,實質是處理Task型別包裹的回撥函式

  • 核心介面:PostTask(Task)、PostDelayTask(Task)、PostRepeatTask(Task)
  • 功能:使用者將Task交給執行緒進行非同步處理,同時實現簡單的Timer機制,對任務觸發的時間進行延遲或重複
  • 面向場景:工作執行緒執行Task
  • 缺點:僅能對Task進行處理,無法充分使用多路複用機制監聽Socket或其他fd
  • 程式碼:TaskLoop-Task

面向Event:

Event型別和Task相比,擁有了事件的語義,Event事件=需要監聽的觸發源+事件處理回撥,一般來說觸發源設計為Event Fd可以完美適配IO多路複用機制,同時僅監聽Event fd可以給上層元件足夠的靈活性,可以認為這是面向事件ThreadLoop很優雅的設計方法了

  • 核心介面:在面向Task的基礎上,增加對事件的監聽AddWatch(Event)
  • 功能:提供檔案描述符監聽的功能
  • 面向場景:事件驅動,需要監聽檔案描述符,例如Socket、Timer,上層僅需設計自己需要的Event即可實現定製化需求,例如進階TaskLoop中的Timer就是很好的例子
  • 具體實踐:
    epoll+Event,基於epoll_data.ptr的指標完成監聽事件的處理,在Event中設定各類事件(或指定事件)的回撥,muduo的channel就是基於這種機制設計的
  • 程式碼:TaskLoop-Event
注意:  
    Event類可以設計為基類,事件處理回撥OnEventCallback可以透過識別Event的不同型別做事件的分發,完成Event內資訊的傳遞,後續也可以透過基類->子類的轉換,實現更多的任務處理和資訊傳遞能力

討論1:
    除了poll多路複用+wakeupFd可以用於實現任務/事件佇列,相似的也可以使用條件變數的方式做事件的同步,但條件變數有喚醒丟失和虛假喚醒等問題
    相較而言FD的多路複用監聽機制更加穩定,同時基於檔案描述符監聽的方式,可以增加基於Timer FD的功能

討論2:
    在具體的實踐中,只要能完成實際的業務需求也可以不用解耦,可直接將業務邏輯回撥寫到secket fd觸發後的邏輯中,設計Event來提供監聽的統一介面是為了通用性,提供可複用介面給其他模組

進階TaskLoop

MsgQueue:TaskLoop+queue

基於TaskLoop,增加佇列queue可實現一個非同步的訊息佇列,提供PostMsg(msg)介面,MsgQueue初始化時繫結訊息處理函式init(callback),

  • 核心原理:MsgQueue內部有一個事件觸發的Event,繫結到TaskLoop中進行監聽,當使用者呼叫PostMsg(msg)向內部的queue壓入訊息時,主動喚醒Event FD來wakeup TaskLoop監聽中的檔案描述符,在Event喚醒的回撥內取出queue積壓的msg,呼叫提前繫結好的callback,完成訊息的非同步處理
  • 核心介面:PostMsg(msg)
  • 面向場景:非同步訊息處理佇列
  • 程式碼:MsgQueue

Timer:TaskLoop+Timer Fd

基於面向Event的TaskLoop,在Timer的場景下,AddWatch(Event)實質上是AddWatch(TimerEventFd),因此只需要在AddWatch(Event)的基礎上做TimerFd建立的封裝即可

  • 核心介面:AddTimer(Task)
  • 面向場景:定時器任務處理
  • 程式碼:TimerMng

ThreadLoopMng

執行緒池,維護多個ThreadLoop的生命週期,派發任務

  • 核心介面:TaskLoop* GetTaskLoop();
  • 面向場景:執行緒池管理工作執行緒,維護執行緒生命週期

相關文章