看了下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()
解析域名為例,
1 2 3 4 5 6 7 8 9 10 11 |
ph_sock_resolve_and_connect() // 回撥func(arg) ph_dns_getaddrinfo() // 設定info->func = did_sys_resolve, info->arg = rac; ph_job_set_pool() // 有事件時 dns_addrinfo(info) // 事件發生時 dns_addrinfo() info->func() // func()即did_sys_resolve() did_sys_resolve() attempt_connect() // 解析成功後發起連線 ph_socket_connect() // 回撥connected_sock() rac->func() // 解析不成功 |
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()
。
1 2 3 4 5 |
ph_thread_spawn() pthread_create() ph_thread_boot() ph_thread_init_myself() init_thread() |
此外,封裝了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_pool
和pending_nbio
是否還有deferred jobpending_pool
中的job根據當前執行緒的tid加入執行緒池對應的buffer,並喚醒等待的consumer(wake_pool()
)pending_nbio
中的job加入到epoll中(_ph_job_set_pool_immediate()
和process_deferred()
)
- 若有等待的producer,喚醒之
- worker執行緒設定自己的
- 啟動
gc_job
sched_loop()
string
程式碼裡面有很長的一段註釋描述了大致的設計和使用方法。其中提到了三個設計目標:
1 2 3 4 5 6 7 8 9 |
/** * ... * * * avoiding heap allocations where possible * * tracking heap allocations when not avoidable * * safely manage string expansion and growth * * ... */ |
字串分為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
- 固定大小的buffer
- embedded
- 在結構中要配合另一個buffer成員來使用
棧上固定大小的buffer不能動態地擴容,只能用到定義時指定的大小空間。 棧上靜態分配的可增長buffer在空間不夠用時會根據指定的memtype進行動態分配,並將memtype改為正數。
通過引用計數來跟蹤動態分配的buffer。引用計數到0的時候(ph_string_delref()
)會根據memtype釋放掉 動態分配的堆記憶體。
記憶體管理
支援使用者自定義的、命名的物件型別,其中指定了這一型別需要分配的大小和一些標記, 例如返回前清零(PH_MEM_FLAGS_ZERO
)。全域性的memtypes
管理了已註冊的所有memtype, 預分配1024個memtype指標。
1 2 3 4 5 6 7 8 |
struct ph_memtype_def { // memory/<facility>/<name> const char *facility; const char *name; uint64_t item_size; unsigned flags; }; typedef struct ph_memtype_def ph_memtype_def_t; |
註冊後(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
型別和相應的介面來維護、查詢某個型別的記憶體分配資訊。