GRPC淺析-completion_queue

pzc1053226965發表於2020-09-25

GRPC淺析-completion_queue


上一節傳送門

前言

completion_queue(以下簡稱cq)有三中型別,分別是GRPC_CQ_NEXT,GRPC_CQ_PLUCK和GRPC_CQ_CALLBACK。

cq_vtable

/*cq的操作介面如下*/
struct cq_vtable {
  grpc_cq_completion_type cq_completion_type;
  size_t data_size;
  void (*init)(void* data,
               grpc_experimental_completion_queue_functor* shutdown_callback);
  void (*shutdown)(grpc_completion_queue* cq);
  void (*destroy)(void* data);
  bool (*begin_op)(grpc_completion_queue* cq, void* tag);
  void (*end_op)(grpc_completion_queue* cq, void* tag, grpc_error* error,
                 void (*done)(void* done_arg, grpc_cq_completion* storage),
                 void* done_arg, grpc_cq_completion* storage, bool internal);
  grpc_event (*next)(grpc_completion_queue* cq, gpr_timespec deadline,
                     void* reserved);
  grpc_event (*pluck)(grpc_completion_queue* cq, void* tag,
                      gpr_timespec deadline, void* reserved);
};

為cq分配記憶體時,會分配額外的cq_vtable::data_size + cq_poller_vtable::size()的記憶體,用於cq_data和poller。poller也就是上一節中提到的pollset。

cq_data有3種,分別是cq_next_data,cq_pluck_data和cq_callback_data,與3種型別的cq分別對應。

cq_next_data內部維護一個完成事件佇列(佇列的push基於CSA,pop基於Spinlock+CSA),work介面會從佇列中消費完成事件。
cq_pluck_data內部維護一個完成事件連結串列,與cq_next_data不同的是,事件不一定是按照FIFO來消費的。並且維護了一個pluckers,當執行緒即將進入(也許會)阻塞情況時,將執行緒對應的woker和tag新增到pluckers裡,當有這個tag型別完成事件時,喚醒執行緒消費完成事件。
cq_callback_data沒有維護完成事件的資料結構。

接下來描述cq_vtable 裡的介面。
init——在初始化cq_data,引數shutdown_callback只有callback型別的cq才使用到。

shutdown——關閉一個cq。callback型別的cq會呼叫init時傳入的shutdown_callback。

destroy——呼叫cq_data解構函式。

begin_op——開始一個完成操作。

end_op——產生一次完成事件。不同的cq,end_op有不同的行為。

  1. 對於next型別cq。
    將完成事件新增到cq_data的佇列裡。如果新增到佇列裡的事件是佇列首個元素,則kick一次worker,這保證執行緒呼叫cq_next時不會處於有完成事件卻阻塞起來的情況。
  2. 對於pluck型別cq。
    將tag完成事件新增到連結串列裡,如果pluckers有此tag對應的worker,則kick此woker。
  3. 對於callback型別cq。
    直接消費完成事件,並排程一個callback。

next——只有next型別cq實現此介面。如果有完成事件,則消費返回。否則,進入pollset_work等待I/O事件。

pluck——只有pluck型別cq實現此介面。如果存在對應tag完成事件,則消費返回,否則等待對應的tag完成事件發生。

cq_poller_vtable

/*poller相關操作也就是上一節所說的pollset*/
struct cq_poller_vtable {
  bool can_get_pollset;
  bool can_listen;
  size_t (*size)(void);
  void (*init)(grpc_pollset* pollset, gpr_mu** mu);
  grpc_error* (*kick)(grpc_pollset* pollset,
                      grpc_pollset_worker* specific_worker);
  grpc_error* (*work)(grpc_pollset* pollset, grpc_pollset_worker** worker,
                      grpc_millis deadline);
  void (*shutdown)(grpc_pollset* pollset, grpc_closure* closure);
  void (*destroy)(grpc_pollset* pollset);
};

有3種型別的poller,分別是GRPC_CQ_DEFAULT_POLLING,GRPC_CQ_NON_LISTENING和GRPC_CQ_NON_POLLING。第一種型別poller可以監聽所有fd,有正常pollset的操作,而第二種出了listening fd不可監聽外,介面行為與第一種完全一樣。第三種,與pollset沒有關聯,也就沒有等待I/O事件的行為。