libco原始碼分析(01) -- Closure原始碼分析
前言
libco是騰訊微信開源的C/C++實現的協程庫,在微信後臺有大規模應用。
在早期微信後臺業務邏輯實現中,大量採用了多程序或多執行緒同步模型。隨著業務量不斷增大,同步模型的弊端日益顯現,在這種場景下,業務系統資料處理能力及整理吞吐量往往非常低。
為了解決此類問題,後臺業務系統必須進行大規模改造,改造的方式有兩種:
- 執行緒非同步化改造;
- 協程非同步化改造;
前一種方式往往要求將現有系統中所有同步模型全部替換為非同步模型,無異於將整個系統從框架層開始全部重寫,風險顯然比較高。而後一種方式,由於hook了socket API,因此將對老系統程式碼的侵入性降到了最低。
libco的專案背景及整體設計思想,可以參考《C/C++協程庫libco:微信怎樣漂亮地完成非同步化改造》
閉包原始碼分析
libco中提供了一份簡單的閉包實現,接下去會分段進行分析。
程式碼片段一:
#ifndef __CO_CLOSURE_H__
#define __CO_CLOSURE_H__
struct stCoClosure_t
{
public:
virtual void exec() = 0;
};
這段程式碼本質上定義了Closure的基類,閉包在呼叫時,最終會呼叫exec()函式。
程式碼片段二:
//1.base //-- 1.1 comac_argc #define comac_get_args_cnt( ... ) comac_arg_n( __VA_ARGS__ ) #define comac_arg_n( _0,_1,_2,_3,_4,_5,_6,_7,N,...) N #define comac_args_seqs() 7,6,5,4,3,2,1,0 #define comac_join_1( x,y ) x##y #define comac_argc( ... ) comac_get_args_cnt( 0,##__VA_ARGS__,comac_args_seqs() ) #define comac_join( x,y) comac_join_1( x,y )
上面定義的6個巨集主要分為以下兩類:
- comac_argc巨集主要用於求出可變巨集引數的個數。 注意:在這份實現中,最多支援7個巨集引數求長度。
- comc_join巨集主要用於將兩個引數拼接為一個引數。
comac_argc巨集展開舉例:
comac_argc( A, B ) -> comac_get_args_cnt( 0, A, B, 7,6,5,4,3,2,1,0 ) -> comac_arg_n( 0, A, B, 7,6,5,4,3,2,1,0 ) -> 2
__VA_ARGS__代表巨集引數是可變的,此處為什麼需要新增字首##呢?
補充說明:
為什麼需要在__VA_ARGS__前新增##?
在gcc中,字首##有一個特殊約定,即當##arg前面是逗號(,)時,如果arg為空,則##之前的逗號(,)將會被自動省去。
因此,當comac_argc()不填寫任何引數時,巨集將會被按照以下方式展開:
comac_argc( ) -> comac_get_args_cnt( 0, 7,6,5,4,3,2,1,0 ) -> comac_arg_n( 0, 7,6,5,4,3,2,1,0 ) -> 0
但是,對於C++11(-std=c++11),如果##arg中的arg為空,則##arg將會被預設轉換為空字串(""),此時,巨集將會按照下面的方式展開:
comac_argc( ) -> comac_get_args_cnt( 0, "", 7,6,5,4,3,2,1,0 ) -> comac_arg_n( 0, "", 7,6,5,4,3,2,1,0) -> 1
程式碼片段三:
//-- 1.2 repeat
#define repeat_0( fun,a,... )
#define repeat_1( fun,a,... ) fun( 1,a,__VA_ARGS__ ) repeat_0( fun,__VA_ARGS__ )
#define repeat_2( fun,a,... ) fun( 2,a,__VA_ARGS__ ) repeat_1( fun,__VA_ARGS__ )
#define repeat_3( fun,a,... ) fun( 3,a,__VA_ARGS__ ) repeat_2( fun,__VA_ARGS__ )
#define repeat_4( fun,a,... ) fun( 4,a,__VA_ARGS__ ) repeat_3( fun,__VA_ARGS__ )
#define repeat_5( fun,a,... ) fun( 5,a,__VA_ARGS__ ) repeat_4( fun,__VA_ARGS__ )
#define repeat_6( fun,a,... ) fun( 6,a,__VA_ARGS__ ) repeat_5( fun,__VA_ARGS__ )
#define repeat( n,fun,... ) comac_join( repeat_,n )( fun,__VA_ARGS__)
上面這些巨集主要用於定義重複操作。在後文中有語句舉例。
程式碼片段四:
//2.implement
#if __cplusplus <= 199711L
#define decl_typeof( i,a,... ) typedef typeof( a ) typeof_##a;
#else
#define decl_typeof( i,a,... ) typedef decltype( a ) typeof_##a;
#endif
#define impl_typeof( i,a,... ) typeof_##a & a;
#define impl_typeof_cpy( i,a,... ) typeof_##a a;
#define con_param_typeof( i,a,... ) typeof_##a & a##r,
#define param_init_typeof( i,a,... ) a(a##r),
1. decl_typeof:主要用於獲取變數a的型別。例如:
int a = 100;
decl_typeof( 1,a);
==>
int a = 100;
typedef typeof(a) typeof_a;
2. impl_typeof:主要用於建立一個和變數a的型別相同的引用。
3. impl_typeof_copy:主要用於建立一個和變數a型別相同的變數。
4. con_param_typeof:用於生成類建構函式入參。
5. param_init_typeof:用於生成類建構函式初始化列表。
程式碼片段五:
//2.1 reference
#define co_ref( name,... )\
repeat( comac_argc(__VA_ARGS__) ,decl_typeof,__VA_ARGS__ )\
class type_##name\
{\
public:\
repeat( comac_argc(__VA_ARGS__) ,impl_typeof,__VA_ARGS__ )\
int _member_cnt;\
type_##name( \
repeat( comac_argc(__VA_ARGS__),con_param_typeof,__VA_ARGS__ ) ... ): \
repeat( comac_argc(__VA_ARGS__),param_init_typeof,__VA_ARGS__ ) _member_cnt(comac_argc(__VA_ARGS__)) \
{}\
} name( __VA_ARGS__ ) ;
上面這段巨集定義,主要用於產生協程入參。例如,有以下一段程式碼:
int a = 100;
co_ref(ref,a);
經過巨集展開後,程式碼如下:
int a = 100;
typedef typeof(a) typeof_a;
class type_ref
{
public:
typeof_a & a;
int _member_cnt;
type_ref(typeof_a & ar, ...)
: a(ar), _member_cnt(1)
{
}
} ref(a);
本質上,就是構造了一個type_ref類,持有了對棧上變數a的引用。
_member_cnt(1)中,成員個數"1"實際上是由巨集comac_argc自動推匯出來的。
程式碼片段六:
//2.2 function
#define co_func(name,...)\
repeat( comac_argc(__VA_ARGS__) ,decl_typeof,__VA_ARGS__ )\
class name:public stCoClosure_t\
{\
public:\
repeat( comac_argc(__VA_ARGS__) ,impl_typeof_cpy,__VA_ARGS__ )\
int _member_cnt;\
public:\
name( repeat( comac_argc(__VA_ARGS__),con_param_typeof,__VA_ARGS__ ) ... ): \
repeat( comac_argc(__VA_ARGS__),param_init_typeof,__VA_ARGS__ ) _member_cnt(comac_argc(__VA_ARGS__))\
{}\
void exec()
#define co_func_end }
上述巨集建立一個協程,程式碼舉例如下:
#include "co_closure.h"
#include <stdio.h>
int main()
{
int a = 100;
co_ref(ref, a);
co_func(f, ref)
{
printf("hello, world!\n");
}
co_func_end;
}
巨集展開之後,程式碼生成如下。可以看到,co_func定義的協程,引用了co_ref定義的引數。
int main()
{
int a = 100;
typedef typeof(a) typeof_a;
class type_ref
{
public:
typeof_a & a;
int _member_cnt;
type_ref(typeof_a & ar, ...) : a(ar), _member_cnt(1)
{
}
} ref(a);;
typedef typeof(ref) typeof_ref;
class f : public stCoClosure_t
{
public:
typeof_ref ref;
int _member_cnt;
public:
f(typeof_ref & refr, ...) : ref(refr), _member_cnt(1)
{
}
void exec()
{
printf("hello, world!\n");
}
};
}
co_func經過巨集展開後,生成了一個名稱為f的類。只要建立這個類的例項,然後呼叫exec()方法,即可執行協程。