熱點推薦:
您现在的位置: 電腦知識網 >> 操作系統 >> Windows優化 >> 正文

淺談 C# 中的代碼協同 (Coroutine) 執行支持

2022-06-13   來源: Windows優化 

  幾個月前我曾大致分析過 C# 中 iterator block 機制的實現原理《C# 中Iterators的改進與實現原理淺析》文中簡要介紹了 C# 是如何在不修改 CLR 的前提下由編譯器通過有限狀態機來實現 iterator block 中 yield 關鍵字
  實際上這一機制的最終目的是提供一個代碼協同執行的支持機制
  以下內容為程序代碼:
  
  using SystemCollectionsGeneric;
  
  public class Tokens : IEnumerable
  {
  public IEnumerator GetEnumerator()
  {
  for(int i = ; i  yield elements[i];
  }
  ...
  }
  
  foreach (string item in new Tokens())
  {
  Console.WriteLine(item);
  }
  
  在這段代碼執行過程中,foreach 的循環體和 GetEnumerator 函數體實際上是在同一個線程中交替執行的。Tw.WINGWIT.Com這是一種介於線程和順序執行之間的協同執行模式,之所以稱之為協同(Coroutine),是因為同時執行的多個代碼塊之間的調度是由邏輯隱式協同完成的。順序執行無所謂並行性,而線程往往是由系統調度程序強制性搶先切換,相對來說Win3.x 中的獨占式多任務倒是與協同模型比較類似。
  就協同執行而言,從功能上可以分為行為、控制兩部分,控制又可進一步細分為控制邏輯和控制狀態。行為對應著如何處理目標對象,如上述代碼中:行為就是將目標對象打印到控制台;控制則是如何遍歷這個 elements 數組,可進一步細分為控制邏輯(順序遍歷)和控制狀態(當前遍歷到哪個元素)。下面將按照這個邏輯介紹不同語言中如何實現和模擬這些邏輯。
  
  Spark Gray 在其 blog 上有一個系列文章介紹了協同執行的一些概念。
  
  Iterators in Ruby (Part - 1)
  Warming up to using Iterators (Part 2)
  
  文章第 1, 2 部分以 Ruby 語言(語法類似 Python)介紹了 Iterator 機制是如何簡化遍歷操作的代碼。實際上中心思想就是將行為與控制分離,由語言層面的支持來降低控制代碼的薄記工作。
  以下內容為程序代碼:
  
  def textfiles(dir)
  Dir.chdir(dir)
  
  Dir["*"].each do |entry|
  yield dir+"\"+entry if /^.*.txt$/ =~ entry
  
  if FileTest.directory?(entry)
  textfiles(entry){|file| yield dir+"\"+file}
  end
  end
  Dir.chdir(".."[img]/images/wink.gif[/img]
  end
  
  textfiles(“c:\”){|file|
  puts file
  }
  
  例如上面這段 Ruby 的遞歸目錄處理代碼中,就采用了與 C# 2.0 中完全類似的語法實現協同執行支持。
  
  對 C# 1.0 和 C++ 這類不支持協同執行的語言,協同執行過程中的狀態遷移或者說執行緒的調度工作,需要由庫和使用者自行實現,例如 STL 中的迭代器 (iterator) 自身必須保存了與遍歷容器相關的位置信息。例如在 STL 中實現協同執行:
  以下內容為程序代碼:
  
  #include
  #include
  #include
  
  // The function object multiplies an element by a Factor
  template
  class MultValue
  {
  private:
  Type Factor;  // The value to multiply by
  public:
  // Constructor initializes the value to multiply by
  MultValue ( const Type& _Val [img]/images/wink.gif[/img] : Factor ( _Val [img]/images/wink.gif[/img] {
  }
  
  // The function call for the element to be multiplied
  void operator ( [img]/images/wink.gif[/img] ( Type& elem [img]/images/wink.gif[/img] const
  {
  elem *= Factor;
  }
  };
  
  int main( [img]/images/wink.gif[/img]
  {
  using namespace std;
  
  vector v1;
  
  //...
  
  // Using for_each to multiply each element by a Factor
  for_each ( v1.begin ( [img]/images/wink.gif[/img] , v1.end ( [img]/images/wink.gif[/img] , MultValue ( -2 [img]/images/wink.gif[/img] [img]/images/wink.gif[/img];
  }
  
  雖然 STL 較為成功的通過迭代器、算法和謂詞,將此協同執行邏輯中的行為和控制分離,謂詞表現行為(MultValue、迭代器(v1.being(), v1.end())表現控制狀態、算法表現控制邏輯(for_each),但仍然存在編寫復雜,使用麻煩,並且語義不連冠的問題。
  一個緩解的方法是將謂詞的定義與控制部分合並到一起,就是類似 boost::Lambda 的實現思路:
  以下內容為程序代碼:
  
  for_each(v.begin(), v.end(), _1 = 1);
  
  for_each(vp.begin(), vp.end(), cout << *_1 << ' ');
  
  通過神奇的模板和宏,可以一定程度降低編寫獨立謂詞來定義行為的復雜度。但控制部分的狀態和邏輯還是需要單獨實現。
  
  而 C# 1.0 中就干脆沒有自帶支持,必須通過《C# 2.0 中Iterators的改進與實現原理淺析》一文中所舉例子那樣笨拙的方式完成。
  以下內容為程序代碼:
  
  public class Tokens : IEnumerable
  {
  public string[] elements;
  
  Tokens(string source, char[] delimiters)
  {
  // Parse the string into tokens:
  elements = source.Split(delimiters);
  }
  
  public IEnumerator GetEnumerator()
  {
  return new TokenEnumerator(this);
  }
  
  // Inner class implements IEnumerator interface:
  private class TokenEnumerator : IEnumerator
  {
  private int position = -1;
  private Tokens t;
  
  public TokenEnumerator(Tokens t)
  {
  this.t = t;
  }
  
  // Declare the MoveNext method required by IEnumerator:
  public bool MoveNext()
  {
  if (position < t.elements.Length - 1)
  {
  position++;
  return true;
  }
  else
  {
  return false;
  }
  }
  
  // Declare the Reset method required by IEnumerator:
  public void Reset()
  {
  position = -1;
  }
  
  // Declare the Current property required by IEnumerator:
  public object Current
  {
  get // get_Current函數
  {
  return t.elements[position];
  }
  }
  }
  ...
  }
  
  這種笨拙的 IEnumerable 接口實現方法,實際上是將 STL 中提供控制狀態的 iterator 完全自行實現,而且控制邏輯還限定於編寫 IEnumerable 接口實現時的定義。就算可以通過策略 (Strategy) 模式提供一定程度的定制,但其代碼邏輯過於分散,要理解一個簡單調用必須查看四五處分散的代碼。
  
  好在牛人總是不缺的,呵呵。
  
  Ajai Shankar 在 MSDN 上一篇非常出色的文章,COROUTINES Implementing Coroutines for .NET by Wrapping the Unmanaged Fiber API,裡面通過 Win32 API 的纖程 (Fiber) 支持和 CLR 幾個底層 API 的支持,完整的實現了一套可用的協同執行支持機制。
  Spark Gray 的第 4 篇文章中就詳細討論了這種實現方式的利弊:
  
  SICP, Fiber api and ITERATORS !(Part 4)
  
  纖程 Fiber 是 Win32 子系統為了移植 Unix 下偽線程環境下的程序方便,而提供的一套輕量級並行執行機制,由程序代碼自行控制調度流程。
  其使用方法很簡單,在某個線程中調用 ConvertThreadToFiber(Ex) 初始化纖程支持,然後調用 CreateFiber(Ex) 建立多個不同纖程,對新建的纖程和轉換時當前線程缺省纖程,都可以通過 SwitchToFiber 顯式進行調度。
  以下內容為程序代碼:
  
  static int array[3] = { 0, 1, 2 };
  
  static int cur = 0;
  
  VOID CALLBACK FiberProc(PVOID lpParameter)
  {
  for(int i=0; i  {
  cur = array[i];
  
  SwitchToFiber(lpParameter);
  }
  }
  
  LPVOID fiberMain = ConvertThreadToFiber(NULL);
  
  LPVOID fiberFor = CreateFiber(0, FiberProc, fiberMain);
  
  while(cur >= 0)
  {
  std::cout << cur << std::endl;
  
  SwitchToFiber(fiberFor);
  }
  
  DeleteFiber(fiberFor);
  
  上述偽代碼是纖程使用的一個大概流程,可以看出實際上纖程跟上面 Ruby 和 C# 2.0 中的協同執行所需功能是非常符合的。而在實現上,纖程實際上是通過在同一線程堆棧中構造出不同的區域(ConvertThreadToFiber/CreateFiber),在 SwitchToFiber 函數中切換到指定區域,以此區域(纖程)的代碼和寄存器等環境執行,有點類似於 C 代碼庫中 longjmp 的概念。Netscape 提供的狀態線程庫 State Threads library 就是通過 longjmp 等機制模擬的類似功能。
  而在 .NET 1.0/1.1 中要使用纖程,則還需要考慮對每個纖程的 Managed 環境構造,以及切換調度時的狀態管理等等。有興趣的朋友可以仔細閱讀上述兩篇精彩文章。
  以下內容為程序代碼:
  
  class CorIter : Fiber {
  protected o
From:http://tw.wingwit.com/Article/os/youhua/201311/10713.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.