基于纖程(Fiber)實現C++異步編程庫(一):原理及示例

纖程(Fiber)和協程(coroutine)是差不多的概念,也叫做用戶級線程或者輕線程之類的。Windows系統提供了一組API用戶創建和使用纖程,本文中的庫就是基于這組API實現的,所以無法跨平臺使用,非Windows程序員可以閃人了,當然如果有興趣可以繼續看下去,找個第三方的協程庫封裝一下,也能實現相同的效果。關于纖程更詳細的信息可以查閱MSDN

纖程的概念中有兩個關鍵點:

  1. 纖程擁有獨立的棧空間和寄存器環境
  2. 纖程在用戶態實現調調度,也就是說完全由程序員控制;

下圖演示了幾個纖程相互切換的過程,注意每個纖程都有獨立的棧,并且通過SwitchToFiber函數切換到其他纖程:

?

作為對比,我們可以看一下函數調用過程中的堆棧變化情況,下面是示意圖,表示了func1 -> func2 -> func3 這種常見的函數嵌套調用關系:

?

?

每一次函數調用都會創建一個新的棧幀(stack frame),合起來就構成整個調用棧,函數返回時其棧幀也隨之釋放。對于函數調用,我們可以確定的一點是(在不拋出異常的情況下)被調用函數執行完畢后一定會在調用點返回并繼續執行下一條語句。但纖程之間的調用(切換)卻不同,一個纖程可以在任意位置切換到其他纖程,并且可能永遠都不會再切換回來,也可能從其他任意纖程(不必是剛剛切換到的)切換回來,前面的示意圖描述的只是一種非常簡單的情況,實際的情況可能非常復雜,復雜到導出都是跳來跳去的箭頭理也理不清。在纖程間切換,有點像用加強版的goto,用的時候固然很爽,但后續的維護卻是個麻煩。

所以就像用while/for/switch-case代替goto一樣,我們也需要封裝一組新的API來代替對操作系統API的直接調用。一方面,在封裝過程中我們可以對纖程的行為(實際是程序員的行為)施加一些安全約束,使得更容易寫出安全的代碼或者更不容易寫出不安全的代碼;另一方面,從goto到while/switch等過程控制語句實際上是一種抽象層次的提升,對大部分常見需求后者用起來更方便,更不容易出錯,寫出的代碼也更簡潔易懂,類似的,從系統API到新的封裝API或者封裝類也是抽象層次的提高,可以更方便的應用在各種業務場景;最后,直接使用系統API需要寫很多維護纖程的輔助代碼,這類代碼通常重復而又分散到業務代碼的各個角落,進一步降低了程序的可讀性和提高了維護難度,封裝也是為了解決這個問題。

好了,廢話說完了,我們先上一段代碼嘗嘗鮮:

 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:控制權移交到主纖程
12         }
13 }); 14 15 for (int i = 0; i < RUN_TIMES; i++) 16 { 17 fib.resume(); // B: 切換到子纖程執行 18 } 19 20 printf("number = %d\r\n", number);

這里先創建了一個纖程實現number變量累加的功能,然后在for循環中執行(姑且用這個詞)最終得到正確的結果。AB兩處代碼分別實現了纖程的切換,實際上是封裝了對SwitchToFiber的調用,注意兩個函數調用細節上的不同:resume表示切換到對象包裝的纖程,是普通成員函數,yield表示控制權移交給調用者纖程,是靜態成員函數,大家可以思考下為什么有靜態和非靜態成員函數的差別。

下面是用纖程實現生產者-消費者模型的代碼:

 1     int product_count = 0;
 2     bool is_end_time = false;
 3 
 4     const int RUN_TIMES = 3;
 5 
 6     // 生產者纖程
 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     // 消費者纖程的執行函數
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     // 創建消費者纖程數組
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     // 等待纖程結束
67     Fiber::await_all(consumer_array);
68     Fiber::await(fib_producer);

程序末尾出現了await和await_all兩個新的方法可以先不用管,不影響主要邏輯。由于所有纖程都是在同一個線程中運行的所以無需加鎖,這也是使用纖程的一個重要好處,也是我們這個封裝庫的主要目的之一。

?

限于篇幅,這次就只寫這么多了,更多的內容將放到后面的帖子中,總計還要寫四、五篇的樣子。但代碼實際上已經寫完了,急性子的園友可以直接到這個地址看代碼:

https://code.csdn.net/xrunning/fiber

?

建了一個QQ群:微觀架構設計165241092,主要討論C++代碼級設計,感興趣的園友加進來一起討論學習。?

轉載于:https://www.cnblogs.com/xrunning/p/4176331.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/273476.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/273476.shtml
英文地址,請注明出處:http://en.pswp.cn/news/273476.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MYSQL讀書筆記---運算符、字符串操作

運算符###########################################,!(<>),>,>,<,< is null , is not null, isnull(expr) expr between min and max expr in(v1,v2,...)流程#############################################mysql> select ifnull(1,0); #如果第一個參數為…

fluidity詳解

fluidity詳解 1.fluidity編譯過程 1.1.femtools庫調用方法 編譯fluidity/femtools目錄下所有文件&#xff0c;打包為libfemtools.a靜態庫文件&#xff1b;通過-lfemtools參數&#xff0c;并指定libfemtools.a靜態庫位置&#xff0c;即可調用 femtools 庫內所有函數2.fluidity主…

mysql讀書筆記---if語句

IF(expr1,expr2,expr3) 如果expr1是TRUE(expr1<>0 and expr1<>NULL)&#xff0c;則IF()的返回值為expr2;否則返回值則為expr3。IF()的返回值為數字值或字符串值&#xff0c;具體情況視其所在語境而定。 mysql>SELECT?IF(1>2,2,3); ->3 mysql>SELECT I…

C語言 將整數寫入內存指定的連續字節單元中

將整數數組寫入0x40003000開始的連續10個字節內存單元中&#xff0c;注意unsigned char *指向一個字節&#xff0c;而int *指向1個字&#xff08;4個字&#xff09;&#xff0c;但是可以把字中存儲的整數放入字節單元中&#xff0c;只要不超過表示的范圍&#xff0c;注意雖然un…

mysql讀書筆記----時間函數

1.獲得當前時間&#xff1a;時間格式yyyy-MM-dd curdate();2.DAYOFWEEK(date) 3.WEEKDAY(date) 4.DAYOFMONTH(date) 5.DAYOFYEAR(date) 6.MONTH(date) 7.DAYNAME(date) 8.MONTHNAME(date) 9.QUARTER(date) 10.WEEK(date) WEEK(date,first) 11.YEAR(date) 12.HOUR(time) 13.MINU…

企業數據1

1. 企業數據 1.1. 全局 1.1.1. 貨幣 CNY Chinese Yuan Renminbi &#xffe5; 6.825020 ARS Argentine Peso $ 3.791090 BOB Bolivian Boliviano $b 7.570800 BRL Brazilian Real R$ 1.766500 CAD Canadian Dollar $ 1.037570 CLP Chilean Peso $ …

sql判斷語句

方法一&#xff1a; <if test"null ! orderType and 0 orderType"> order by amountTimes desc </if><if test"null ! orderType and 1 orderType">order by amountTimes asc </if><if test"null ! orderType and 2 …

Thinkphp 整合tcpdf

網上查了些關于tcpdf 使用教程&#xff0c;整合到TP的話&#xff0c;會有些小問題&#xff0c;由于基礎還不是很扎實&#xff0c;花了點時間終于整合OK了。下面介紹步驟&#xff1a; 環境&#xff1a; TP版本&#xff1a;TP3.2.2 tcpdf:tcpdf_6_2_3 1. 將tcpdf_6_2_3.zip解壓在…

多項目開發下的dll文件管理

閱讀目錄&#xff1a;DS01&#xff1a;為什么要對生成的dll文件進行管理&#xff1f;DS02&#xff1a;首先介紹以下兩個DOS命令DS03&#xff1a;第一種實現方法&#xff08;xcopy&#xff09;DS04&#xff1a;第二種實現方法&#xff08;attrib&#xff09;DS05&#xff1a;分享…

關于Java中的隨機數產生

對比兩種寫法&#xff1a; 第一種&#xff1a; public static void main(String args[]){Random random new Random(System.currentTimeMillis());for(int i0; i<20; i){int sindex random.nextInt(2);System.out.println(sindex);}}第二種&#xff1a; public static voi…

視圖

視圖是虛表&#xff0c;是從一個或幾個基本表&#xff08;或視圖&#xff09;中導出的表&#xff0c;在系統的數據字典中僅存放了視圖的定義&#xff0c;不存放視圖對應的數據。視圖是原始數據庫數據的一種變換&#xff0c;是查看表中數據的另外一種方式。可以將視圖看成是一個…

選擇器的并發性

4.3.4 并發性 選擇器對象是線程安全的&#xff0c;但它們包含的鍵集合不是。通過keys( )和selectKeys( )返回的鍵的集合是Selector對象內部的私有的Set對象集合的直接引用。這些集合可能在任意時間被改變。已注冊的鍵的集合是只讀的。如果您試圖修改它&#xff0c;那么您得到的…

mysql 自定義函數之判斷

DELIMITER $$CREATE DEFINERrootlocalhost FUNCTION getMin(a int,b int) RETURNS int(11)BEGINdeclare min int;if(a>b)then set min b;elseif(b>a)then set min a;else set min 0;#end if;end if;RETURN min;END 調用該函數可以如下方式 select getMin(1,2); 返回值…

C/C++的數組名

數組名相當于指向數組第一個元素的地址。數組名不是變量&#xff0c;是地址常量&#xff0c;不能為其賦值。如下&#xff1a;1&#xff09;一維數組中對于數組 a[5] {1, 2, 3, 4, 5};數組名a相當于指向第一個元素a[0]的指針。即 a 與 &a[0] 等價。2&#xff09;二維數組中…

mysql的運算法

一、算術運算符1、加 www.2cto.com mysql> select 12;-----| 12 |-----| 3 |-----2、減mysql> select 1-2;-----| 1-2 |-----| -1 |-----3、乘mysql> select 2*3;-----| 2*3 |-----| 6 |-----4、除mysql> select 2/3;--------| 2/3 |--------| 0.6667 |-…

轉-- iOS 30多個iOS常用動畫,帶詳細注釋

// // CoreAnimationEffect.h // CoreAnimationEffect // // Created by VincentXue on 13-1-19. // Copyright (c) 2013年 VincentXue. All rights reserved. //#import <Foundation/Foundation.h>/**! 導入QuartzCore.framework** Example:** Step.1** #imp…

Java中abstract與interface

抽象類&#xff08;abstract class&#xff09;的特點&#xff1a; 1.抽象類、抽象方法都必須使用abstract修飾。 2.抽象類中&#xff0c;可以有非抽象方法&#xff0c;甚至可以是沒有任何方法或變量的空類。 對于抽象類中不定義抽象方法的用意在于&#xff1a;使該類不能被創建…

按位與、或、異或等運算方法

按位與運算符&#xff08;&&#xff09; 參加運算的兩個數據&#xff0c;按二進制位進行“與”運算。 運算規則&#xff1a;0&00; 0&10; 1&00; 1&11; 即&#xff1a;兩位同時為“1”&#xff0c;結果才為“1”&#xff0c;否則為0 例如&#xff1…

JavaScript驗證

<script type"text/javascript"> /*密碼*/ function password() { var password document.getElementById("password").value; var ts document.getElementById("tsPassword"); if (password.length >…

mysql數據庫根據上傳的經緯度計算距離

select 6371.393*ACOS(COS(RADIANS(latitude))*COS(RADIANS(47.02))*COS(RADIANS(longitude)-RADIANS(114.100))SIN(RADIANS(latitude))*SIN(RADIANS(47.02))) as distancefrom location