偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

C++多進(jìn)程并發(fā)框架

開(kāi)發(fā) 后端
三年來(lái)一直從事服務(wù)器程序開(kāi)發(fā),一直都是忙忙碌碌,不久前結(jié)束了職業(yè)生涯的第一份工作,有了一個(gè)禮拜的休息時(shí)間,終于可以寫(xiě)寫(xiě)總結(jié)了。于是把以前的開(kāi)源代碼做了整理和優(yōu)化,這就是FFLIB。

三年來(lái)一直從事服務(wù)器程序開(kāi)發(fā),一直都是忙忙碌碌,不久前結(jié)束了職業(yè)生涯的***份工作,有了一個(gè)禮拜的休息時(shí)間,終于可以寫(xiě)寫(xiě)總結(jié)了。于是把以前的開(kāi)源代碼做了整理和優(yōu)化,這就是FFLIB。雖然這邊總結(jié)看起來(lái)像日記,有很多廢話(huà),但是此文仍然是有很大針對(duì)性的。針對(duì)服務(wù)器開(kāi)發(fā)中常見(jiàn)的問(wèn)題,如多線(xiàn)程并發(fā)、消息轉(zhuǎn)發(fā)、異步、性能優(yōu)化、單元測(cè)試,提出自己的見(jiàn)解。

面對(duì)的問(wèn)題

從事開(kāi)發(fā)工程中,遇到過(guò)不少問(wèn)題,很多時(shí)候由于時(shí)間緊迫,沒(méi)有使用優(yōu)雅的方案。在跟業(yè)內(nèi)的一些朋友交流過(guò)程中,我也意識(shí)到有些問(wèn)題是大家都存在的。簡(jiǎn)單列舉如下:

  • 多線(xiàn)程與并發(fā)
  • 異步消息/接口調(diào)用
  • 消息的序列化與Reflection
  • 性能優(yōu)化
  • 單元測(cè)試

多線(xiàn)程與并發(fā)

現(xiàn)在是多核時(shí)代,并發(fā)才能實(shí)現(xiàn)更高的吞吐量、更快的響應(yīng),但也是把雙刃劍。總結(jié)如下幾個(gè)用法:

  • 多線(xiàn)程+顯示鎖;接口是被多線(xiàn)程調(diào)用的,當(dāng)被調(diào)用時(shí),顯示加鎖,再操作實(shí)體數(shù)據(jù)。悲劇的是,工程師為了優(yōu)化會(huì)設(shè)計(jì)多個(gè)鎖,以減少鎖的粒度,甚至有些地方使用了原子操作。這些都為領(lǐng)域邏輯增加了額外的設(shè)計(jì)負(fù)擔(dān)。最壞的情況是會(huì)出現(xiàn)死鎖。
  • 多線(xiàn)程+任務(wù)隊(duì)列;接口被多線(xiàn)程調(diào)用,但請(qǐng)求會(huì)被暫存到任務(wù)隊(duì)列,而任務(wù)隊(duì)列會(huì)被單線(xiàn)程不斷執(zhí)行,典型生產(chǎn)者消費(fèi)者模式。它的并發(fā)在于不同的接口可以使用不同的任務(wù)隊(duì)列。這也是我最常用的并發(fā)方式。

這是兩種最常見(jiàn)的多線(xiàn)程并發(fā),它們有個(gè)天生的缺陷——Scalability。一個(gè)機(jī)器的性能總是有瓶頸的。兩個(gè)場(chǎng)景的邏輯雖然由多個(gè)線(xiàn)程實(shí)現(xiàn)了并發(fā),但是運(yùn)算量十分有可能是一臺(tái)機(jī)器無(wú)法承載的。如果是多進(jìn)程并發(fā),那么可以分布式把其部署到其他機(jī)器(也可部署在一臺(tái)機(jī)器)。所以多進(jìn)程并發(fā)比多線(xiàn)程并發(fā)更加Scalability。另外采用多進(jìn)程后,每個(gè)進(jìn)程單線(xiàn)程設(shè)計(jì),這樣的程序更加Simplicity。多進(jìn)程的其他優(yōu)點(diǎn)如解耦、模塊化、方便調(diào)試、方便重用等就不贅言了。

異步消息/接口調(diào)用

提到分布式,就要說(shuō)一下分布式的通訊技術(shù)。常用的方式如下:

  • 類(lèi)RPC;包括WebService、RPC、ICE等,特點(diǎn)是遠(yuǎn)程同步調(diào)用。遠(yuǎn)程的接口和本地的接口非常相似。但是游戲服務(wù)器程序一般非常在意延遲和吞吐量,所以這些阻塞線(xiàn)程的同步遠(yuǎn)程調(diào)用方式并不常用。但是我們必須意識(shí)到他的優(yōu)點(diǎn),就是非常利于調(diào)用和測(cè)試。
  • 全異步消息;當(dāng)調(diào)用遠(yuǎn)程接口的時(shí)候,異步發(fā)送請(qǐng)求消息,接口響應(yīng)后返回一個(gè)結(jié)果消息,調(diào)用方的回調(diào)函數(shù)處理結(jié)果消息繼續(xù)邏輯操作。所以有些邏輯就會(huì)被切割成ServiceStart和ServiceCallback兩段。有時(shí)異步會(huì)講領(lǐng)域邏輯變得支離破碎。另外消息處理函數(shù)中一般會(huì)寫(xiě)一坨的 switch/case 處理不同的消息。***的問(wèn)題在于單元測(cè)試,這種情況傳統(tǒng)單元測(cè)試根本束手無(wú)策。

消息的序列化與Reflection

實(shí)現(xiàn)消息的序列化和反序列化的方式有很多,常見(jiàn)的有Struct、json、Protobuff等都有很成功的應(yīng)用。我個(gè)人傾向于使用輕量級(jí)的二進(jìn)制序列化,優(yōu)點(diǎn)是比較透明和高效,一切在掌握之中。在FFLIB 中實(shí)現(xiàn)了bin_encoder_t 和 bin_decoder_t 輕量級(jí)的消息序列化,幾十行代碼而已。

性能優(yōu)化

已經(jīng)寫(xiě)過(guò)關(guān)于性能方面的總結(jié),參見(jiàn)

http://www.cnblogs.com/zhiranok/archive/2012/06/06/cpp_perf.html

有的網(wǎng)友提到profiler、cpuprofiler、callgrind等工具。這些工具我都使用過(guò),說(shuō)實(shí)話(huà),對(duì)于我來(lái)說(shuō),我太認(rèn)同它有很高的價(jià)值。***他們只能用于開(kāi)發(fā)測(cè)試階段,可以初步得到一些性能上參考數(shù)據(jù)。第二它們?nèi)绾螌?shí)現(xiàn)跟蹤人們無(wú)從得知。運(yùn)行其會(huì)使程序變慢,不能反映真實(shí)數(shù)據(jù)。第三重要的是,開(kāi)發(fā)測(cè)試階段性能和上線(xiàn)后的能一樣嗎?Impossible !

關(guān)于性能,原則就是數(shù)據(jù)說(shuō)話(huà),詳見(jiàn)博文,不在贅述。

單元測(cè)試

關(guān)于單元測(cè)試,前邊已經(jīng)談?wù)摿艘恍?。游戲服?wù)器程序一般都比較龐大,但是不可思議的是,鄙人從來(lái)沒(méi)見(jiàn)有項(xiàng)目(c++ 后臺(tái)架構(gòu)的)有完整單元測(cè)試的。由于存在著異步和多線(xiàn)程,傳統(tǒng)的單元測(cè)試框架無(wú)法勝任,而開(kāi)發(fā)支持異步的測(cè)試框架又是不現(xiàn)實(shí)的。我們必須看到的是,傳統(tǒng)的單元測(cè)試框架已經(jīng)取得了非常大的成功。據(jù)我了解,使用web 架構(gòu)的游戲后臺(tái)已經(jīng)對(duì)于單元測(cè)試的使用已經(jīng)非常成熟,取得了極其好的效果。所以我的思路是利用現(xiàn)有的單元測(cè)試框架,將異步消息、多線(xiàn)程的架構(gòu)做出調(diào)整。

已經(jīng)多次談?wù)搯卧獪y(cè)試了。其實(shí)在開(kāi)發(fā)FFLIB的思路很大程度來(lái)源于此,否則可能只是一個(gè)c++ 網(wǎng)絡(luò)庫(kù)而已。我決定嘗試去解決這個(gè)問(wèn)題的時(shí)候,把FFLIB 定位于框架。

先來(lái)看一段非常簡(jiǎn)單的單元測(cè)試的代碼 :

  1. Assert(2 == Add(11)); 

請(qǐng)?jiān)试S我對(duì)這行代碼做些解釋?zhuān)瑢?duì)Add函數(shù)輸入?yún)?shù),驗(yàn)證返回值是否是預(yù)期的結(jié)果。這不就是單元測(cè)試的本質(zhì)嗎?在想一下我們異步發(fā)送消息的過(guò)程,如果每個(gè)輸入消息約定一個(gè)結(jié)果消息包,每次發(fā)送請(qǐng)求時(shí)都綁定一個(gè)回調(diào)函數(shù)接收和驗(yàn)證結(jié)果消息包。這樣的話(huà)就恰恰滿(mǎn)足了傳統(tǒng)單元測(cè)試的步驟了。***還需解決一個(gè)問(wèn)題,Assert是不能處理異步的返回值的。幸運(yùn)的是,future機(jī)制可以化異步為同步。不了解future 模式的可以參考這里:

http://blog.chinaunix.net/uid-23093301-id-190969.html

http://msdn.microsoft.com/zh-cn/library/dd764564.aspx#Y300

來(lái)看一下在FFLIB框架下遠(yuǎn)程調(diào)用echo 服務(wù)的示例:

  1. struct lambda_t  
  2. {  
  3.   static void callback(echo_t::out_t& msg_)  
  4.   {  
  5.     echo_t::in_t in;  
  6.     in.value = "XXX_echo_test_XXX";  
  7.     singleton_t<msg_bus_t>::instance()  
  8.        .get_service_group("echo")  
  9.        ->get_service(1)->async_call(in, &lambda_t::callback);  
  10.   }  
  11. };  
  12. echo_t::in_t in;  
  13. in.value = "XXX_echo_test_XXX";  
  14. singleton_t<msg_bus_t>::instance().get_service_group("echo")->get_service(1)->async_call(in, &lambda_t::callback); 

當(dāng)需要調(diào)用遠(yuǎn)程接口時(shí),async_call(in, &lambda_t::callback); 異步調(diào)用必須綁定一個(gè)回調(diào)函數(shù),回調(diào)函數(shù)接收結(jié)果消息,可以觸發(fā)后續(xù)操作。這樣的話(huà),如果對(duì)echo 的遠(yuǎn)程接口做單元測(cè)試,可以這樣做:

  1. rpc_future_t< echo_t::out_t> rpc_future;  
  2. echo_t::in_t in;  
  3. in.value = "XXX_echo_test_XXX";  
  4. const echo_t::out_t& out = rpc_future.call(  
  5.     singleton_t<msg_bus_t>::instance()  
  6.         .get_service_group("echo")->get_service(1), in);  
  7. Assert(in.value == out.value); 

這樣所有的遠(yuǎn)程接口都可以被單元測(cè)試覆蓋。

FFLIB 介紹

 FFLIB 結(jié)構(gòu)圖

如圖所示,Client 不會(huì)直接和Service 相連接,而是通過(guò)Broker 中間層完成了消息傳遞。關(guān)于Broker 模式可以參見(jiàn):http://blog.chinaunix.net/uid-23093301-id-90459.html

進(jìn)程間通信采用TPC,而不是多線(xiàn)程使用的共享內(nèi)存方式。Service 一般是單線(xiàn)程架構(gòu)的,通過(guò)啟動(dòng)多進(jìn)程實(shí)現(xiàn)相對(duì)于多線(xiàn)程的并發(fā)。由于Broker模式天生石分布式的,所以有很好的Scalability。

消息時(shí)序圖

#p#

如何注冊(cè)服務(wù)和接口

來(lái)看一下Echo 服務(wù)的實(shí)現(xiàn):

  1. struct echo_service_t  
  2. {  
  3. public:  
  4.     void echo(echo_t::in_t& in_msg_, rpc_callcack_t<echo_t::out_t>& cb_)  
  5.     {  
  6.         logtrace((FF, "echo_service_t::echo done value<%s>", in_msg_.value.c_str()));  
  7.         echo_t::out_t out;  
  8.         out.value = in_msg_.value;  
  9.         cb_(out);  
  10.     }  
  11. };  
  12.  
  13. int main(int argc, char* argv[])  
  14. {  
  15.     int g_index = 1;  
  16.     if (argc > 1)  
  17.     {  
  18.         g_index = atoi(argv[1]);  
  19.     }  
  20.     char buff[128];  
  21.     snprintf(buff, sizeof(buff), "tcp://%s:%s""127.0.0.1""10241");  
  22.  
  23.     msg_bus_t msg_bus;  
  24.     assert(0 == singleton_t<msg_bus_t>::instance().open("tcp://127.0.0.1:10241") && "can't connnect to broker");  
  25.  
  26.     echo_service_t f;  
  27.  
  28.     singleton_t<msg_bus_t>::instance().create_service_group("echo");  
  29.     singleton_t<msg_bus_t>::instance().create_service("echo", g_index)  
  30.             .bind_service(&f)  
  31.             .reg(&echo_service_t::echo);  
  32.  
  33.     signal_helper_t::wait();  
  34.  
  35.     singleton_t<msg_bus_t>::instance().close();  
  36.     //usleep(1000);  
  37.     cout <<"\noh end\n";  
  38.     return 0;  
  • create_service_group 創(chuàng)建一個(gè)服務(wù)group,一個(gè)服務(wù)組可能有多個(gè)并行的實(shí)例  
  • create_service 以特定的id 創(chuàng)建一個(gè)服務(wù)實(shí)例
  • reg 為該服務(wù)注冊(cè)接口
  • 接口的定義規(guī)范為void echo(echo_t::in_t& in_msg_, rpc_callcack_t<echo_t::out_t>& cb_),***個(gè)參數(shù)為輸入的消息struct,第二個(gè)參數(shù)為回調(diào)函數(shù)的模板特例,模板參數(shù)為返回消息的struct 類(lèi)型。接口無(wú)需知道發(fā)送消息等細(xì)節(jié),只需將結(jié)果callback 即可。
  • 注冊(cè)到Broker 后,所有Client都可獲取該服務(wù)

消息定義的規(guī)范

我們約定每個(gè)接口(遠(yuǎn)程或本地都應(yīng)滿(mǎn)足)都包含一個(gè)輸入消息和一個(gè)結(jié)果消息。來(lái)看一下echo 服務(wù)的消息定義:

  1. struct echo_t  
  2. {  
  3.     struct in_t: public msg_i  
  4.     {  
  5.         in_t():  
  6.             msg_i("echo_t::in_t")  
  7.         {}  
  8.         virtual string encode()  
  9.         {  
  10.             return (init_encoder() << value).get_buff();  
  11.         }  
  12.         virtual void decode(const string& src_buff_)  
  13.         {  
  14.             init_decoder(src_buff_) >> value;  
  15.         }  
  16.  
  17.         string value;  
  18.     };  
  19.     struct out_t: public msg_i  
  20.     {  
  21.         out_t():  
  22.             msg_i("echo_t::out_t")  
  23.         {}  
  24.         virtual string encode()  
  25.         {  
  26.             return (init_encoder() << value).get_buff();  
  27.         }  
  28.         virtual void decode(const string& src_buff_)  
  29.         {  
  30.             init_decoder(src_buff_) >> value;  
  31.         }  
  32.  
  33.         string value;  
  34.     };  
  35. }; 
  •  每個(gè)接口必須包含in_t消息和out_t消息,并且他們定義在接口名(如echo _t)的內(nèi)部
  • 所有消息都繼承于msg_i, 其封裝了二進(jìn)制的序列化、反序列化等。構(gòu)造時(shí)賦予類(lèi)型名作為消息的名稱(chēng)。
  • 每個(gè)消息必須實(shí)現(xiàn)encode 和 decode 函數(shù)

這里需要指出的是,F(xiàn)FLIB 中不需要為每個(gè)消息定義對(duì)應(yīng)的CMD。當(dāng)接口如echo向Broker 注冊(cè)時(shí),reg接口通過(guò)C++ 模板的類(lèi)型推斷會(huì)自動(dòng)將該msg name 注冊(cè)給Broker, Broker為每個(gè)msg name 分配唯一的msg_id。Msg_bus 中自動(dòng)維護(hù)了msg_name 和msg_id 的映射。Msg_i 的定義如下:

  1. struct msg_i : public codec_i  
  2. {  
  3.     msg_i(const char* msg_name_):  
  4.         cmd(0),  
  5.         uuid(0),  
  6.         service_group_id(0),  
  7.         service_id(0),  
  8.         msg_id(0),  
  9.         msg_name(msg_name_)  
  10.     {}  
  11.  
  12.     void set(uint16_t group_id, uint16_t id_, uint32_t uuid_, uint16_t msg_id_)  
  13.     {  
  14.         service_group_id = group_id;  
  15.         service_id       = id_;  
  16.         uuid             = uuid_;  
  17.         msg_id           = msg_id_;  
  18.     }  
  19.  
  20.     uint16_t cmd;  
  21.     uint16_t get_group_id()   constreturn service_group_id; }  
  22.     uint16_t get_service_id() constreturn service_id;       }  
  23.     uint32_t get_uuid()       constreturn uuid;             }  
  24.  
  25.     uint16_t get_msg_id()     constreturn msg_id;           }  
  26.     const string& get_name()  const 
  27.     {  
  28.         if (msg_name.empty() == false)  
  29.         {  
  30.             return msg_name;  
  31.         }  
  32.         return singleton_t<msg_name_store_t>::instance().id_to_name(this->get_msg_id());  
  33.     }  
  34.  
  35.     void     set_uuid(uint32_t id_)   { uuid = id_;  }  
  36.     void     set_msg_id(uint16_t id_) { msg_id = id_;}  
  37.     void     set_sgid(uint16_t sgid_) { service_group_id = sgid_;}  
  38.     void     set_sid(uint16_t sid_)   { service_id = sid_; }  
  39.     uint32_t uuid;  
  40.     uint16_t service_group_id;  
  41.     uint16_t service_id;  
  42.     uint16_t msg_id;  
  43.     string   msg_name;  
  44.  
  45.     virtual string encode(uint16_t cmd_)  
  46.     {  
  47.         this->cmd = cmd_;  
  48.         return encode();  
  49.     }  
  50.     virtual string encode() = 0;  
  51.     bin_encoder_t& init_encoder()  
  52.     {  
  53.         return encoder.init(cmd)  << uuid << service_group_id << service_id<< msg_id;  
  54.     }  
  55.     bin_encoder_t& init_encoder(uint16_t cmd_)  
  56.     {  
  57.         return encoder.init(cmd_) << uuid << service_group_id << service_id << msg_id;  
  58.     }  
  59.     bin_decoder_t& init_decoder(const string& buff_)  
  60.     {  
  61.         return decoder.init(buff_) >> uuid >> service_group_id >> service_id >> msg_id;  
  62.     }  
  63.     bin_decoder_t decoder;  
  64.     bin_encoder_t encoder;  
  65. }; 

關(guān)于性能

由于遠(yuǎn)程接口的調(diào)用必須通過(guò)Broker, Broker會(huì)為每個(gè)接口自動(dòng)生成性能統(tǒng)計(jì)數(shù)據(jù),并每10分鐘輸出到perf.txt 文件中。文件格式為CSV,參見(jiàn):

http://www.cnblogs.com/zhiranok/archive/2012/06/06/cpp_perf.html

總結(jié)

FFLIB框架擁有如下的特點(diǎn):

  • 使用多進(jìn)程并發(fā)。Broker 把Client 和Service 的位置透明化
  • Service 的接口要注冊(cè)到Broker, 所有連接Broker的Client 都可以調(diào)用(publisher/ subscriber)
  • 遠(yuǎn)程調(diào)用必須綁定回調(diào)函數(shù)
  • 利用future 模式實(shí)現(xiàn)同步,從而支持單元測(cè)試
  • 消息定義規(guī)范簡(jiǎn)單直接高效
  • 所有service的接口性能監(jiān)控?cái)?shù)據(jù)自動(dòng)生成,免費(fèi)的午餐
  • Service 單線(xiàn)程話(huà),更simplicity

源代碼:

Svn co http://ffown.googlecode.com/svn/trunk/

運(yùn)行示例:

  • Cd example/broker && make && ./app_broker –l http://127.0.0.1:10241
  • Cd example/echo_server && make && ./app_echo_server
  • Cd example/echo_client && make && ./app_echo_client

原文鏈接:C++多進(jìn)程并發(fā)框架

責(zé)任編輯:林師授 來(lái)源: mysqlops.com
相關(guān)推薦

2012-08-09 08:56:34

C++

2025-07-02 01:00:00

2017-06-30 10:12:46

Python多進(jìn)程

2022-04-01 13:10:20

C++服務(wù)器代碼

2015-04-21 13:37:44

Google開(kāi)源CC++版

2024-12-27 08:11:44

Python編程模式IO

2010-07-15 12:51:17

Perl多進(jìn)程

2010-02-01 10:54:37

C++框架

2024-09-29 10:39:14

并發(fā)Python多線(xiàn)程

2024-01-03 10:03:26

PythonTCP服務(wù)器

2024-03-29 06:44:55

Python多進(jìn)程模塊工具

2016-01-11 10:29:36

Docker容器容器技術(shù)

2021-10-12 09:52:30

Webpack 前端多進(jìn)程打包

2020-10-20 17:35:42

srpcRPC語(yǔ)言

2025-01-27 00:54:31

2010-01-14 14:17:20

Visual C++

2024-08-26 08:39:26

PHP孤兒進(jìn)程僵尸進(jìn)程

2019-02-26 11:15:25

進(jìn)程多線(xiàn)程多進(jìn)程

2009-04-21 09:12:45

Java多進(jìn)程運(yùn)行

2021-02-25 11:19:37

谷歌Android開(kāi)發(fā)者
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)