基于纤程(Fiber)实现C++异步编程库(一):原理及示例
纖程(Fiber)和協(xié)程(coroutine)是差不多的概念,也叫做用戶級(jí)線程或者輕線程之類的。Windows系統(tǒng)提供了一組API用戶創(chuàng)建和使用纖程,本文中的庫(kù)就是基于這組API實(shí)現(xiàn)的,所以無(wú)法跨平臺(tái)使用,非Windows程序員可以閃人了,當(dāng)然如果有興趣可以繼續(xù)看下去,找個(gè)第三方的協(xié)程庫(kù)封裝一下,也能實(shí)現(xiàn)相同的效果。關(guān)于纖程更詳細(xì)的信息可以查閱MSDN。
纖程的概念中有兩個(gè)關(guān)鍵點(diǎn):
下圖演示了幾個(gè)纖程相互切換的過(guò)程,注意每個(gè)纖程都有獨(dú)立的棧,并且通過(guò)SwitchToFiber函數(shù)切換到其他纖程:
?
作為對(duì)比,我們可以看一下函數(shù)調(diào)用過(guò)程中的堆棧變化情況,下面是示意圖,表示了func1 -> func2 -> func3 這種常見(jiàn)的函數(shù)嵌套調(diào)用關(guān)系:
?
?
每一次函數(shù)調(diào)用都會(huì)創(chuàng)建一個(gè)新的棧幀(stack frame),合起來(lái)就構(gòu)成整個(gè)調(diào)用棧,函數(shù)返回時(shí)其棧幀也隨之釋放。對(duì)于函數(shù)調(diào)用,我們可以確定的一點(diǎn)是(在不拋出異常的情況下)被調(diào)用函數(shù)執(zhí)行完畢后一定會(huì)在調(diào)用點(diǎn)返回并繼續(xù)執(zhí)行下一條語(yǔ)句。但纖程之間的調(diào)用(切換)卻不同,一個(gè)纖程可以在任意位置切換到其他纖程,并且可能永遠(yuǎn)都不會(huì)再切換回來(lái),也可能從其他任意纖程(不必是剛剛切換到的)切換回來(lái),前面的示意圖描述的只是一種非常簡(jiǎn)單的情況,實(shí)際的情況可能非常復(fù)雜,復(fù)雜到導(dǎo)出都是跳來(lái)跳去的箭頭理也理不清。在纖程間切換,有點(diǎn)像用加強(qiáng)版的goto,用的時(shí)候固然很爽,但后續(xù)的維護(hù)卻是個(gè)麻煩。
所以就像用while/for/switch-case代替goto一樣,我們也需要封裝一組新的API來(lái)代替對(duì)操作系統(tǒng)API的直接調(diào)用。一方面,在封裝過(guò)程中我們可以對(duì)纖程的行為(實(shí)際是程序員的行為)施加一些安全約束,使得更容易寫出安全的代碼或者更不容易寫出不安全的代碼;另一方面,從goto到while/switch等過(guò)程控制語(yǔ)句實(shí)際上是一種抽象層次的提升,對(duì)大部分常見(jiàn)需求后者用起來(lái)更方便,更不容易出錯(cuò),寫出的代碼也更簡(jiǎn)潔易懂,類似的,從系統(tǒng)API到新的封裝API或者封裝類也是抽象層次的提高,可以更方便的應(yīng)用在各種業(yè)務(wù)場(chǎng)景;最后,直接使用系統(tǒng)API需要寫很多維護(hù)纖程的輔助代碼,這類代碼通常重復(fù)而又分散到業(yè)務(wù)代碼的各個(gè)角落,進(jìn)一步降低了程序的可讀性和提高了維護(hù)難度,封裝也是為了解決這個(gè)問(wèn)題。
好了,廢話說(shuō)完了,我們先上一段代碼嘗嘗鮮:
1 const int RUN_TIMES = 5; 2 3 int number = 0; 4 bool shutdown = false; 5 6 Fiber fib([&number, &shutdown] 7 { 8 while (!shutdown) 9 { 10 number++; 11 Fiber::yield(); // A:控制權(quán)移交到主纖程 12 }13 }); 14 15 for (int i = 0; i < RUN_TIMES; i++) 16 { 17 fib.resume(); // B: 切換到子纖程執(zhí)行 18 } 19 20 printf("number = %d\r\n", number);
這里先創(chuàng)建了一個(gè)纖程實(shí)現(xiàn)number變量累加的功能,然后在for循環(huán)中執(zhí)行(姑且用這個(gè)詞)最終得到正確的結(jié)果。AB兩處代碼分別實(shí)現(xiàn)了纖程的切換,實(shí)際上是封裝了對(duì)SwitchToFiber的調(diào)用,注意兩個(gè)函數(shù)調(diào)用細(xì)節(jié)上的不同:resume表示切換到對(duì)象包裝的纖程,是普通成員函數(shù),yield表示控制權(quán)移交給調(diào)用者纖程,是靜態(tài)成員函數(shù),大家可以思考下為什么有靜態(tài)和非靜態(tài)成員函數(shù)的差別。
下面是用纖程實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者模型的代碼:
1 int product_count = 0; 2 bool is_end_time = false; 3 4 const int RUN_TIMES = 3; 5 6 // 生產(chǎn)者纖程 7 Fiber fib_producer([&is_end_time, &product_count] 8 { 9 srand((unsigned)time(NULL)); 10 11 while (!is_end_time) 12 { 13 int new_product_count = (int)((double)rand() / RAND_MAX * 10) + 1; 14 product_count += new_product_count; 15 16 printf("[producer] create new products: %d\r\n", new_product_count); 17 18 Fiber::yield(); 19 } 20 21 printf("[producer] off duty.\r\n"); 22 }); 23 24 // 消費(fèi)者纖程的執(zhí)行函數(shù) 25 auto consumer_proc = [&is_end_time, &product_count](const int seq_number) 26 { 27 int total_count = 0; 28 29 while (!is_end_time) 30 { 31 if (product_count > 0) 32 { 33 product_count--; 34 total_count++; 35 printf("[consumer %d] got 1 product, total got %d, remain %d\r\n", seq_number, total_count, product_count); 36 } 37 38 Fiber::yield(); 39 } 40 41 printf("[consumer %d] off duty.\r\n", seq_number); 42 }; 43 44 const int CONSUMER_COUNT = 3; 45 int consumer_seq_number = 0; 46 47 // 創(chuàng)建消費(fèi)者纖程數(shù)組 48 std::vector<Fiber> consumer_array(CONSUMER_COUNT); 49 std::for_each(consumer_array.begin(), consumer_array.end(), [&](Fiber& item){ item = Fiber([&]{ consumer_proc(consumer_seq_number); }); consumer_seq_number++; }); 50 51 consumer_seq_number = 0; 52 53 for (int i = 0; i < RUN_TIMES; i++) 54 { 55 fib_producer.resume(); 56 57 while (product_count > 0) 58 { 59 consumer_array[consumer_seq_number].resume(); 60 consumer_seq_number = (consumer_seq_number + 1) % CONSUMER_COUNT; 61 } 62 } 63 64 is_end_time = true; 65 66 // 等待纖程結(jié)束 67 Fiber::await_all(consumer_array); 68 Fiber::await(fib_producer);程序末尾出現(xiàn)了await和await_all兩個(gè)新的方法可以先不用管,不影響主要邏輯。由于所有纖程都是在同一個(gè)線程中運(yùn)行的所以無(wú)需加鎖,這也是使用纖程的一個(gè)重要好處,也是我們這個(gè)封裝庫(kù)的主要目的之一。
?
限于篇幅,這次就只寫這么多了,更多的內(nèi)容將放到后面的帖子中,總計(jì)還要寫四、五篇的樣子。但代碼實(shí)際上已經(jīng)寫完了,急性子的園友可以直接到這個(gè)地址看代碼:
https://code.csdn.net/xrunning/fiber
?
建了一個(gè)QQ群:微觀架構(gòu)設(shè)計(jì)165241092,主要討論C++代碼級(jí)設(shè)計(jì),感興趣的園友加進(jìn)來(lái)一起討論學(xué)習(xí)。?
轉(zhuǎn)載于:https://www.cnblogs.com/xrunning/p/4176331.html
總結(jié)
以上是生活随笔為你收集整理的基于纤程(Fiber)实现C++异步编程库(一):原理及示例的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: #飘#公交车美女理论乱谈!
- 下一篇: c# mysql app.config_