libphenom 原始碼筆記

發表於2014-05-06

看了下Facebook前段時間放出來的libphenom,簡單總結一下。

【轉註】:libPhenom是Facebook釋出的一個C語言事件框架,用於構建高效能和高可擴充套件的系統。支援多執行緒、提供記憶體管理和常用資料結構、json處理。

網路相關

libphenom提供了自定義的socket描述符ph_socket_t和通用的地址結構phenom_sockaddr。 ph_sock結構封裝了讀寫buffer、用於NBIO的job結構、超時時長、事件發生後的callback等資訊。 被enable的ph_sock將由NBIO pool管理。

監聽和連線都是封裝成job來非同步化:

  • ph_socket_connect(s, addr, timeout, func, arg)
    • s上發起對addr的非同步連線,超時時間timeout,結束後(可能連線失敗)呼叫func(arg)。這個callback的型別為ph_socket_connect_func,若出錯則status為對應的errno
  • ph_sock_resolve_and_connect(),解析域名併發起連線。
    • 根據resolver引數指定的resolver解析域名。如果是PH_SOCK_CONNECT_RESOLVE_SYSTEM,呼叫ph_dns_getaddrinfo(),將解析相關的資料、callback封裝為job後通過ph_job_set_pool()加入DNS執行緒池。如果是PH_SOCK_CONNECT_RESOLVE_ARES則呼叫ARES庫解析。
  • 一個listener的job被排程時,呼叫其callback accept_dispatch()。這個函式通過accept4()accept()來接收新連線,並對客戶端socket呼叫listener上的acceptor()

以通過getaddrinfo()解析域名為例,

Job

Job的定義ph_job_def包含callback、memtype和destructor。 新建立的job會根據其定義中的memtype來分配記憶體,並設定callback。 每種具體的job,例如ph_listener_t,都有對應的ph_job_def

建立一個job時,呼叫ph_job_alloc(),傳入job對應的定義來獲取和初始化動態分配的物件。

執行緒池

Phenom執行緒上記錄了

  • pending NBIO job 佇列
  • pending pool job 佇列
  • pthread_t 執行緒id
  • 在pool中的結點
  • name

每個phenom執行緒分配一個全域性唯一的id,對應一個pthread執行緒。 如註釋所說,tid < MAX_RINGS的phenom執行緒稱為preferred thread, 擁有自己專用的job佇列,其他執行緒競爭共享佇列,用spinlock同步。

全域性的pools將所有執行緒池儲存在連結串列中。其中包含用於consumer和producer等待/喚醒的結構(futex或condition variable), 儲存job的ring buffer、worker執行緒的指標等等資訊。

ph_thread_spawn(func, arg)建立一個ph_thread_t執行緒。 實際上是呼叫pthread_create(),讓其執行ph_thread_boot(),將實際要執行的函式func() 和引數arg等資訊傳入。ph_thread_boot()會分配記憶體並建立一個新的ph_thread_t結構, 執行一些初始化,然後呼叫傳入的那個func()

此外,封裝了join、self、setaffinity等等pthread操作。

NBIO

用timer wheel管理timer,設定wheel interval 100ms,初始100ms。

全域性的timer_job,監視timerfd上的讀事件,觸發tick_epoll(),使timer wheel往前跳一個tick,處理當前的定時任務。

ph_nbio_init()初始化NBIO。

  • calloc()分配num_schedulers個emitter
  • 初始化每個emitter及其timer wheel(ph_timerwheel_init()ph_nbio_emitter_init()
    • timer_fd描述符(timerfd_create()
    • timer_job扔進pending_nbio佇列,這個job被排程到時執行tick_epoll
  • 初始化相關的counter

ph_sched_run()啟動job排程。

  • emitters中的phenom執行緒emitters[1 .. num_threads]執行sched_loop()emitters[0]ph_sched_run()的呼叫者
    • 設定is_worker
    • 設定CPU affinity
    • 設定執行緒名
    • epoll_emitter()
  • 啟動worker。對pools中每個執行緒池,其中每個執行緒pool->threads[0 .. pool->max_workers]執行worker_thread()
    • worker執行緒設定自己的is_worker標記
    • 根據自己的tid選出對應的buffer,設定CPU affinity
    • 進入迴圈,不斷地彈出job(pop_job()),執行job的callback。彈出job的時候,首先根據tid嘗試自己的佇列,否則從其它執行緒的佇列裡取,還取不出則wait_pool()
    • 檢查pending_poolpending_nbio是否還有deferred job
      • pending_pool中的job根據當前執行緒的tid加入執行緒池對應的buffer,並喚醒等待的consumer(wake_pool()
      • pending_nbio中的job加入到epoll中(_ph_job_set_pool_immediate()process_deferred()
    • 若有等待的producer,喚醒之
  • 啟動gc_job
  • sched_loop()

string

程式碼裡面有很長的一段註釋描述了大致的設計和使用方法。其中提到了三個設計目標:

字串分為stack-based和embedded兩種

  • stack-based
    • 固定大小的buffer
      • PH_STRING_DECLARE_STACK(name, size)
      • memtype為PH_STRING_STATIC
    • 可增長的buffer
      • PH_STRING_DECLARE_GROW(name, size, memtype)
      • memtype為PH_STRING_GROW_MT(mt),即-mt。這裡用負的memtype表示stack-based growable,正的memtype表示heap-allocated growable
  • embedded
    • 在結構中要配合另一個buffer成員來使用

棧上固定大小的buffer不能動態地擴容,只能用到定義時指定的大小空間。 棧上靜態分配的可增長buffer在空間不夠用時會根據指定的memtype進行動態分配,並將memtype改為正數。

通過引用計數來跟蹤動態分配的buffer。引用計數到0的時候(ph_string_delref())會根據memtype釋放掉 動態分配的堆記憶體。

記憶體管理

支援使用者自定義的、命名的物件型別,其中指定了這一型別需要分配的大小和一些標記, 例如返回前清零(PH_MEM_FLAGS_ZERO)。全域性的memtypes管理了已註冊的所有memtype, 預分配1024個memtype指標。

註冊後(ph_memtype_register())得到一個型別為ph_memtype_t的“描述符”, 以後引用這個型別的時候使用的都是這個“描述符”。

分配和釋放的各個介面:

  • 分配固定大小:ph_mem_alloc(mt),根據mt中記錄的型別大小來分配
  • 分配不定大小:ph_mem_alloc_size(mt, size),分配size + HEADER_RESERVATION。這個header中記錄了本次分配的大小和memtype
  • 釋放:ph_mem_free(mt, ptr),根據memtype或header來釋放

ph_mem_stats型別和相應的介面來維護、查詢某個型別的記憶體分配資訊。

 

相關文章