VC驿站

 找回密码
 加入驿站

QQ登录

只需一步,快速开始

搜索
查看: 1134|回复: 0

[交流] golang channel 的 c++ 實現

[复制链接]
74_avatar_middle
在线会员 发表于 2016-10-24 14:15:10 | 显示全部楼层 |阅读模式
本帖最后由 zuiwuchang 于 2016-10-24 14:29 编辑

golang 中最有價值的兩個東西分別是 goroutine 和 channel
使用goroutine 可以讓其它語言中 反人類的異步代碼 改為同步 卻保持同樣/或許 更好的效率
使用 channel 可以在並發代碼中 去掉 明確的 鎖操作
以孤的愚見 要在 c++中 實現 goroutine的東西 恐怕是徒勞的 因為即便可以實現 同樣高效的 協程 也無法讓這個 協程 和系統api 第三方庫 無縫的/或者容易配合的 使用
然 channel 卻可以 很好的 在 c++中 工作 故孤嘗試 寫了一個 可用在 c++ 中的 Channel

如果你已經 玩過 golang (沒玩過 的話 真的該去玩下 有編程基礎 1週不到 就可以玩會 之後服務器相關的 東西 你不會再想使用go之外的 語言去編寫)
那你基本 就知道該如何使用 孤實現的 Channel 了 其使用方式 基本同 go 只是現在 使用 Write Read 函數 讀寫 Channel
而且使用 Channel 傳輸的數據 必須滿足 copy 語義 且存在 不拋出異常的無參構造函數

此  Channel 使用了 c++11 特性 請確認 你的編程器 是否支持(不支持的話 你應該扔掉手上的垃圾 換上 真的工具了)
其次 此Channel 使用了 boost的 幾個組件 其中 boost thread 庫 是需要編譯的 這意味着 thread依賴的 data_time system 庫 亦要編譯

下面開始 Channel 的說明和 example
  1. template<typename T,std::size_t N=0>
  2.         class Channel
复制代码

模板類 Channel 需要一個 傳輸 型別 和一個緩衝區大小 創建一個 T 的 傳輸 通道 如你所見 緩存大小默認為 0


  1. //這是 一個 int 的 傳輸 通道
  2. Channel<int> ch;
  3. Channel<int> ch1 = ch;
  4. Channel<int> ch2 = std::move(ch);
复制代码

如你所見 Channel 支持了 copy 和 move 語義 所以你可以方便的 存儲她

  1. //寫入 數據 到 Channel (如果 寫入完成前 Channel 被關閉 返回 false)
  2. inline bool Write(const T& v)
复制代码

如果 緩存大小為0 那麼 Write 將一直不返回 直到 有代碼 調用了 Read 數據 將 Write的數據取出 才會返回
(這和 golang channel 一樣)

  1. //從 Channel 讀取數據 (如果 讀取完成前 Channel 被關閉 返回 false)
  2.             inline bool Read(T& v)
  3.             
复制代码

如果沒有 可讀 數據 Read 將一直 阻塞 直到 存在可讀數據 或者在 Channel被關閉時返回false

  1. //返回Channel是否關閉
  2. inline bool IsClose()
复制代码

  1. //關閉 Channel
  2. inline void Close()
复制代码

關閉 Channel 可以通知 在阻塞中 的 Write Read 返回 false 以便結束 一個 while(Read)/while(Write)

所有對外暴露 接口 只有上面 四個 所以 使用 非常簡單
下面是 一個 生產消費 模型的 示例 (使用 Channel 將或相當簡單)
  1. #include <iostream>
  2. #include <cstdlib>
  3. #include <ctime>
  4. #include <king/thread/Channel.hpp>

  5. //
  6. #define BUFFER_SIZE 0
  7. typedef king::thread::Channel<int,BUFFER_SIZE> Channel;

  8. //std::cout 同步
  9. boost::mutex G_m;

  10. //生產者
  11. void producer(Channel ch)
  12. {
  13.     int v;
  14.     while(true)
  15.     {
  16.         v = std::rand() % 100;
  17.         if(!ch.Write(v))
  18.         {
  19.             //channel 已關閉
  20.             break;
  21.         }
  22.     }
  23. }

  24. //消費者
  25. void consumer(Channel ch)
  26. {
  27.     int v;
  28.     while(true)
  29.     {
  30.         if(ch.Read(v))
  31.         {
  32.             boost::unique_lock<boost::mutex> lock(G_m);
  33.             std::cout<<"consumer "<<v<<"\n";
  34.         }
  35.         else
  36.         {
  37.             //channel 已關閉
  38.             break;
  39.         }
  40.     }
  41. }

  42. int main()
  43. {
  44.     std::srand(std::time(NULL));

  45.     //定義 通道
  46.     Channel ch;

  47.     boost::thread_group threads;

  48.     //創建 生產者 線程
  49.     threads.create_thread(boost::bind(producer,ch));
  50.     threads.create_thread(boost::bind(producer,ch));
  51.     threads.create_thread(boost::bind(producer,ch));
  52.     threads.create_thread(boost::bind(producer,ch));

  53.     //定義 消費者 線程
  54.     threads.create_thread(boost::bind(consumer,ch));
  55.     threads.create_thread(boost::bind(consumer,ch));
  56.     threads.create_thread(boost::bind(consumer,ch));
  57.     threads.create_thread(boost::bind(consumer,ch));

  58.     threads.join_all();

  59.     return 0;
  60. }
复制代码




golang 提供了 select 可以在多個 channel 上等待
由於 孤的無知 故只想到了 使用多個線程分別 等待多個 channel 之後 在通知主線程的 方法
(或者可以 不使用 多線程 而使用一個 輪詢的方式 然孤是極度 厭惡 輪詢的 故只提供了 多線程 等待的方式)
這寫起來可能會比較 麻煩 雖然孤提供了 幾個宏 來簡化 這一操作 然比起golang select 依然是 繁瑣許多

1 首先 select 代碼 必須放在 KING_SELECT_CHANNEL_BEGIN 和 KING_SELECT_CHANNEL_END 宏 之間
2 在 上面兩個宏中 需要 跟上 許多 KING_SELECT_CHANNEL_THREAD(CHAN,N) ... 宏
  每個 KING_SELECT_CHANNEL_THREAD 定義一個要等待的 channel
  CHAN 是需要等待的 Channel
  N 是一個任意的std::size_t 作為 和 Channel關聯的 標記(所以 多個 Channel 的N 不能一樣)
  
  KING_SELECT_CHANNEL_THREAD宏會 啟動一個 boost::thread 線程 持續 從 CHAN 中 Read 數據 直到 CHAN 關閉
  在 Read 到數據 或 CHAN 關閉 時 會向 定義select 的線程 發送一個 通知
3 在定義完 等待線程 後 需要 寫上 KING_SELECT_CHANNEL_SWITCH 宏
  KING_SELECT_CHANNEL_SWITCH 會構造 一個 switch 語句 同時 在 收到 CHAN 通知時 將其 派發到 與CHAN關聯的 case N: 代碼塊上執行
4 在步驟3之後 需要 跟上處理代碼 多個 KING_SELECT_CHANNEL_CASE(CHAN,N,VAR,OPEN,CODE)
  每個 KING_SELECT_CHANNEL_CASE 會在 switch 中 創建一個 case N: 代碼段 CHAN 和 N 參數 同 KING_SELECT_CHANNEL_THREAD
  對應的 channel 有數據 到達 就會執行 相應  的  case 代碼
  VAR 是一個 自定義 變量名 宏 會自動創建 一個 變量 並在數據到達時 將數據 寫入到 此變量
  OPEN 是一個 自定義 變量名 宏 會自動創建 一個 bool 變量 如果 數據到達 會將其設置為 true 如果 是由於CHAN被關閉 而得到的 通知 會將其設置為 false
  CODE 需要實現的 是任意的 邏輯 代碼 相當於 case N: 和break;間的 語句 可以使用 VAR OPEN 變量 進行邏輯處理

KING_SELECT_CHANNEL_BEGIN 和 KING_SELECT_CHANNEL_END 宏  被包含在一個 while(true) 中 將 一直 等待是否 有 數據到達 channel
直到所有 被 select 的 channel 都 關閉 KING_SELECT_CHANNEL_BEGIN 和 KING_SELECT_CHANNEL_END 宏 才會 退出 開始執行KING_SELECT_CHANNEL_END之後的代碼

一個 select 代碼 一般 會像是如下的代碼
  1.     KING_SELECT_CHANNEL_BEGIN
  2.     KING_SELECT_CHANNEL_THREAD(ch0,0)
  3.     KING_SELECT_CHANNEL_THREAD(ch1,1)
  4.     ... other channel
  5.     KING_SELECT_CHANNEL_SWITCH
  6.     KING_SELECT_CHANNEL_CASE(ch0,0,v0,open,
  7.     {
  8.         if(open)
  9.         { do work by v0}
  10.         else
  11.         { do close}
  12.     })
  13.     KING_SELECT_CHANNEL_CASE(ch1,1,v1,open,
  14.     {
  15.         if(open)
  16.         { do work by v1}
  17.         else
  18.         { do close}
  19.     })
  20.     ... other channel
  21.     KING_SELECT_CHANNEL_END
复制代码



下面是 一個 select的示例
  1. #include <iostream>
  2. #include <cstdlib>
  3. #include <ctime>
  4. #include <boost/lexical_cast.hpp>
  5. #include <king/thread/Channel.hpp>

  6. //
  7. #define BUFFER_SIZE 0
  8. typedef king::thread::Channel<int,BUFFER_SIZE> ChannelInt;
  9. typedef king::thread::Channel<std::string,BUFFER_SIZE> ChannelStr;


  10. //生產者
  11. void producer_int(ChannelInt ch)
  12. {
  13.     int v;
  14.     while(true)
  15.     {
  16.         v = std::rand() % 100;
  17.         if(!ch.Write(v))
  18.         {
  19.             //channel 已關閉
  20.             break;
  21.         }
  22.     }
  23. }
  24. void producer_str(ChannelStr ch)
  25. {
  26.     std::string v;
  27.     while(true)
  28.     {
  29.         v = boost::lexical_cast<std::string>(std::rand() % 100);
  30.         if(!ch.Write(v))
  31.         {
  32.             //channel 已關閉
  33.             break;
  34.         }
  35.     }
  36. }

  37. int main()
  38. {
  39.     std::srand(std::time(NULL));

  40.     //定義 通道
  41.     ChannelInt ch_int;
  42.     ChannelStr ch_str;

  43.     boost::thread_group threads;

  44.     //創建 生產者 線程
  45.     threads.create_thread(boost::bind(producer_int,ch_int));
  46.     threads.create_thread(boost::bind(producer_int,ch_int));
  47.     threads.create_thread(boost::bind(producer_str,ch_str));
  48.     threads.create_thread(boost::bind(producer_str,ch_str));


  49.     //select
  50.     std::size_t sum_int = 0;
  51.     std::size_t sum_str = 0;
  52.     KING_SELECT_CHANNEL_BEGIN
  53.     KING_SELECT_CHANNEL_THREAD(ch_int,0)
  54.     KING_SELECT_CHANNEL_THREAD(ch_str,1)
  55.     KING_SELECT_CHANNEL_SWITCH
  56.     KING_SELECT_CHANNEL_CASE(ch_int,0,val,open,
  57.     {
  58.         if(open)
  59.         {
  60.             std::cout<<"ch_int value = "<<val<<"\n";
  61.             if(sum_int++>10)
  62.             {
  63.                 ch_int.Close();
  64.             }
  65.         }
  66.         else
  67.         {
  68.             std::cout<<"ch_int close\n";
  69.         }
  70.     })
  71.     KING_SELECT_CHANNEL_CASE(ch_str,1,val,open,
  72.     {
  73.         if(open)
  74.         {
  75.             std::cout<<"ch_str value = "<<val<<"\n";
  76.             if(sum_str++>10)
  77.             {
  78.                 ch_str.Close();
  79.             }
  80.         }
  81.         else
  82.         {
  83.             std::cout<<"ch_str close\n";
  84.         }
  85.     })
  86.     KING_SELECT_CHANNEL_END


  87.     return 0;
  88. }
复制代码


詳情見 https://github.com/zuiwuchang/ki ... /thread/Channel.hpp

king.zip

6.47 KB, 下载次数: 0, 下载积分: 驿站币 -1





上一篇:求VC高手编程
下一篇:关于管理员SID与管理员权限,有个疑惑,麻烦请知道的解答一下,谢谢
您需要登录后才可以回帖 登录 | 加入驿站 qq_login

本版积分规则

关闭

站长提醒上一条 /2 下一条

QQ|小黑屋|手机版|VC驿站 ( 辽ICP备09019393号tongdun|网站地图wx_jqr

GMT+8, 2019-5-24 16:13

Powered by Discuz! X3.4

© 2009-2019 cctry.com

快速回复 返回顶部 返回列表