<form id="hz9zz"></form>
  • <form id="hz9zz"></form>

      <nobr id="hz9zz"></nobr>

      <form id="hz9zz"></form>

    1. 明輝手游網中心:是一個免費提供流行視頻軟件教程、在線學習分享的學習平臺!

      使用.NET Remoting完成并行計算

      [摘要][簡介]過去,做一個并行計算的試驗要費九牛二虎之力,今天,有了.NET Remoting,我們只需要完成非常少的編程工作,便可以跨多臺計算機輕松進行分布計算。在本文中,Eric Bergman-Te...
      [簡介]
      過去,做一個并行計算的試驗要費九牛二虎之力,今天,有了.NET Remoting,我們只需要完成非常少的編程工作,便可以跨多臺計算機輕松進行分布計算。在本文中,Eric Bergman-Terrell創建了一個名為Digits of Pi的應用程序,它使用并行的多臺計算機以不可思議的精度計算π值。他設法在12小時內完成了10,000位數的計算,卻只使用了相當少的計算資源。這比用一臺計算機單獨完成計算快了300%。

      歡迎進入.NET Remoting的奇妙世界!在這篇文章里,您將與我一起,親自動手體驗并行計算的威力。為了方便您更好地理解這篇文章,請首先按照下面的步驟作一番準備:
      1.從附增光盤獲取示例應用程序及源代碼。
      2.打開Everything.sln解決方案。此解決方案包含運行“Digits of Pi”應用程序所需的三個項目(Client、Server和ServerLoader)。還包含一個名為SimpleClient的項目。加載Everything.sln之后,請選擇Build(編譯) Batch Build...(批編譯...)。單擊Select All(全部選定)按鈕,然后單擊Build(編譯)。編譯所有內容后,請在本地計算機以及您的LAN中的遠程計算機上安裝該軟件。
      3.在本地計算機上,創建一個文件夾并將以下文件復制到其中:
      Server\bin\Release\Plouffe_Bellard.dll
      Client\bin\Release\DigitsOfPi.exe
      4.在每個遠程計算機和本地計算機上,創建一個文件夾并將以下文件復制到其中:

      Server\bin\Release\Plouffe_Bellard.dll
      ServerLoader\bin\Release\ServerLoader.exe
      ServerLoader\ServerLoader.exe.config

      5.然后運行ServerLoader.exe程序。當然,運行ServerLoader和Digits of Pi程序之前,需要在每臺計算機上安裝.NET Framework。

      在所有遠程計算機和本地計算機上運行ServerLoader程序后,請運行Digits of Pi程序。單擊Configure...(配置...)(參見圖1),添加本地計算機名和遠程計算機名。如果不確定某臺計算機的名稱,請查看ServerLoader程序,它在表中顯示其計算機名。如果您很幸運地擁有一個多CPU系統,您只需為所有CPU輸入一次計算機名。只需在計算機名后鍵入@符號和一個編號。例如,如果您擁有一個名為“Brainiac”的雙CPU系統,則鍵入以下計算機名:“Brainiac@1”和“Brainiac@2”。為多CPU系統輸入多個計算機名可以確保所有計算機的CPU都用于計算π值。輸入所有計算機名后,單擊OK(確定)。
      指定要計算的位數(參見圖2)并單擊Calculate(計算)。請從較少的位數開始,π值小數點后面的位數越多,程序所需的時間就越長。
      圖3顯示了Digits of Pi程序如何在本地計算機和遠程計算機中分配工作量,它使用TCP/IP端口9000發送請求并接收結果。接下來,我們將詳細探討Remoting、Plouffe_Bellard服務器對象、ServerLoader程序、SimpleClient程序和Digits of Pi程序。

      服務器對象
      服務器對象將計算指定的九位π值。它被命名為Plouffe_Bellard,因為它使用Fabrice Bellard的增強Simon Plouffe算法。雖然存在更快的算法,但Plouffe-Bellard算法非常簡單(少于300行源代碼),它使用少量的內存,并且由于九位數字可以單獨計算,因此更適于并行執行。Plouffe_Bellard.CalculatePiDigits方法將計算在指定位置開始的九位π值。例如,CalculatePiDigits(0)從第一位開始返回九位數字:141592653。CalculatePiDigits(9)從第十位開始返回九位數字,依此類推。

      ServerLoader
      ServerLoader程序將加載服務器對象,指定通過LAN訪問服務器對象的協議和端口,偵聽來自客戶端程序的傳入調用,處理調用并返回結果。特別值得注意的是,所有這些只需一行代碼便可完成,只需通過使用配置文件的路徑調用RemotingConfiguration.Configure方法。ServerLoader程序將加載名為ServerLoader.exe.config的配置文件(參見代碼段1)。此配置文件指定以SingleCall模式加載服務器對象,即每個傳入調用都由服務器對象的一個新實例處理。如果服務器對象以Singleton模式加載,每個傳入調用都將由同一個實例處理。類型屬性指定服務器對象的完整類型名稱(包括PB命名空間)及其程序集的名稱。objectUri屬性指定對象的統一資源標識符(URI)的端點。<channel>元素指定使用TCP協議,端口9000訪問服務器對象。
      代碼段1:ServerLoader.exe.config
      <configuration>
      <system.runtime.remoting>
      <application name = "ServerLoader">
      <service>
      <wellknown
      mode="SingleCall"
      type="PB.Plouffe_Bellard,Plouffe_Bellard"
      objectUri="Plouffe_Bellard"/>
      </service>
      <channels>
      <channel ref="tcp server" port="9000"/>
      </channels>
      </application>
      </system.runtime.remoting>
      </configuration>
      SimpleClient

      我創建了一個名為SimpleClient的程序,以說明客戶端程序訪問遠程計算機上的服務器對象是多么容易。要運行SimpleClient,首先在遠程計算機上運行ServerLoader,然后在本地計算機上運行SimpleClient.exe程序。在Remote Machine(遠程計算機)文本框中輸入遠程計算機的名稱,然后單擊Calculate(計算)按鈕開始計算第一個九位π值。SimpleClient的CalculateButton_Click方法包含客戶端訪問遠程服務器所需的所有代碼(參見代碼段2)。可以使用由遠程計算機名、協議(TCP)和端口號(9000)組成的URL訪問遠程服務器。例如,要訪問我的“Pentium 200”計算機,則URL為“tcp://Pentium 200:9000/ServerLoader/Plouffe_Bellard”。創建URL后,將使用服務器的類型(Plouffe_Bellard)和URL調用Activator.GetObject。然后,返回的值被轉換為Plouffe_Bellard對象以備使用。調用其CalculatePiDigits方法時,請求被發送到遠程計算機上的ServerLoader。然后,服務器對象計算小數位。最后,在一個文本框中顯示返回客戶端程序的結果。
      代碼段2:用于訪問遠程服務器的SimpleClient代碼

      private void CalculateButton_Click(object sender,System.EventArgs e)
      {
      Cursor.Current = Cursors.WaitCursor;
      Plouffe_Bellard PiCalculator = null;
      String MachineName = RemoteMachineTextBox.Text;
      try
      {
      int port = 9000;
      String URL = "tcp://" + MachineName + ":" +
      port + "/ServerLoader/Plouffe_Bellard";
      PiCalculator = (Plouffe_Bellard)
      Activator.GetObject(typeof(Plouffe_Bellard), URL);
      ResultsTextBox.Text = "3." +
      PiCalculator.CalculatePiDigits(1);
      }
      catch(Exception)
      {
      MessageBox.Show(
      "需要在計算機" +
      MachineName,
      "Simple Client上運行ServerLoader.exe",
      MessageBoxButtons.OK,
      MessageBoxIcon.Error);
      }
      Cursor.Current = Cursors.Arrow;
      }


      Digits of Pi客戶端
      Digits of Pi客戶端程序比SimpleClient更復雜。SimpleClient僅通過訪問遠程計算機上的服務器對象來計算前九位π值。而Digits of Pi則同時使用Configure(配置)對話框中指定的遠程計算機和本地計算機(如圖1所示)并行計算用戶指定的小數位。服務器對象在單獨的線程中訪問,以便在可能需要很長時間的計算過程中保持Digits of Pi GUI對用戶操作的響應性。
      Digits of Pi使用數組將作業分為九位數據塊,將工作量分配到所有可用的計算機上。用戶單擊Calculate(計算)按鈕后,將創建SolutionArray(參見圖4)。SolutionArray為要計算的每組九位π值分配一個SolutionItem元素。服務器對象計算m_Digit字段指定的九位數組后,數位將存儲在m_Results成員中。m_MachineName成員包含運行服務器的計算機的名稱。存儲計算機名是為了使Digits of Pi能夠顯示每臺計算機計算的小數總數(參見圖2)。
      為使服務器對象并行計算,Digits of Pi將為每個服務器對象創建一個線程并啟動線程計算。然后,必須等待所有線程完成計算后才能顯示最終結果。WaitHandle對于等待多個線程很有用。Digits of Pi將為每個線程使用一個WaitHandle,以等待所有線程完成計算。
      將調用CalculationThread.Calculate(參見代碼段3)以便為每個服務器對象創建一個線程。該操作將啟動線程運行,然后返回一個AutoResetEvent(從WaitHandle衍生而來)。每個線程的AutoResetEvent都存儲在一個數組中,然后數組被傳遞給WaitHandle.WaitAll。完成線程計算后,將對其AutoResetEvent調用Set方法。最后一個線程調用Set方法后,將返回WaitAll調用,并顯示π的值。
      代碼段3:CalculationThread。
      public static WaitHandle Calculate(
      SolutionArray solutionArray, String machineName)
      {
      CalculationThread calculationThread = new
      CalculationThread(solutionArray, machineName);
      Thread thread = new Thread(new
      ThreadStart(calculationThread.Calculate));
      thread.Start();
      return calculationThread.calculationDone;
      }

      每個線程都使用相同的算法:如果有更多的工作要處理,線程將奪取下一個SolutionItem,在SolutionItem中存儲服務器對象的計算機名,計算指定的九位小數,并將結果存儲在SolutionItem中。此進程將一直運行,直到所有SolutionItem中都填充了結果。有關詳細信息,請參見代碼段4。
      代碼段4:CalculationThread.Calculate
      public void Calculate()
      {
      Plouffe_Bellard PiCalculator =
      RemotePiCalculator.GetPiCalculator(
      GetRealMachineName(machineName));
      if (PiCalculator != null)
      {
      SolutionItem Item = null;
      bool Abort;
      do
      {
      Abort = solutionArray.Abort;
      if (!Abort)
      {
      Item = solutionArray.GetNextItem();
      if (Item != null)
      {
      Item.MachineName = machineName;
      try
      {
      Item.Results =
      PiCalculator.CalculatePiDigits(Item.Digit);
      }
      catch (Exception e)
      {
      Abort = true;
      MessageBox.Show(
      "無法訪問主機上的遠程對象" +
      machineName +
      Environment.NewLine +
      Environment.NewLine +
      "Message: " +
      e.Message, Globals.ProgramName,
      MessageBoxButtons.OK,
      MessageBoxIcon.Error);
      }
      UpdateStatisticsDelegate USD = new
      UpdateStatisticsDelegate(
      MF.UpdateStatistics);
      MF.Invoke(USD, new Object[] {} );
      }
      }
      } while (Item != null && !Abort);
      calculationDone.Set();
      }
      }

      下面是每一步的說明:
      1. GetRealMachineName從多CPU計算機名中刪除@1模式。例如,GetRealMachineName("Brainiac@1")返回“Brainiac”。有關多CPU計算機名的解釋,請參見圖1對話框中的文本。
      2.知道正確的計算機名后,將其傳遞給RemotePiCalculator.
      GetPiCalculator,這樣才可以通過PiCalculator變量訪問該計算機上的服務器對象。
      3. 如果用戶單擊了Cancel(取消)按鈕,將設置Abort屬性。如果Abort屬性為true,線程將停止計算。
      4. 對MF.Invoke的調用使線程可以安全地更新ListView中的統計數據(參見圖2),即使該ListView是由另一個線程創建的。在32位Windows編程中,絕不允許在創建某個控件的線程之外處理該控件。
      5. 完成循環(即計算完指定的所有π位數或者用戶單擊Cancel [取消]按鈕)后,將調用線程的AutoResetEvent的Set函數。
      6. 當每個線程都調用其AutoResetEvent的Set函數后,將返回對WaitHandle.WaitAll的調用并顯示結果。

      線程同步
      如果Digits of Pi的代碼由多個線程同時訪問,可能會有多個地方出現錯誤。例如,如果兩個線程同時調用SolutionArray.GetNextItem,可能會返回相同的內容。這就是在GetNextItem方法中設置[MethodImpl(MethodImplOptions.Synchronized)]屬性的原因,該屬性可以確保一次只有一個線程調用該方法。如果方法的每一行代碼都不應由多個線程同時訪問,則使方法同步是一個很好的策略。
      由于MainForm.Calculate方法只有一行代碼不能同時被多個線程訪問,因此它將在該行代碼之前調用Monitor.Enter,并在其后調用Monitor.Exit。如果該行代碼已在其他線程上運行,Monitor.Enter將被阻止。如果整個函數已實現同步,那么只保護需要防止多個線程訪問的代碼行就可以提高性能。

      從System.Windows.Forms.Control派生的對象(例如Button、TextBox、RichTextBox、Label、ListBox、ListView等等)只應由創建它們的線程處理。要從非創建線程中安全處理Control衍生對象,請首先將處理代碼放入一個方法,然后為該方法聲明一個代理:

      delegate void SetResultsTextDelegate(String Text);

      private void SetResultsText(String Text)
      {
      ResultsRichTextBox.Text = Text;
      }

      然后使用Form.Invoke間接調用該方法:

      SetResultsTextDelegate SRTD = new
      SetResultsTextDelegate(SetResultsText);

      Invoke(SRTD, new object[] { "" } );

      Invoke方法將從創建它的線程中調用該方法,它使用的參數與對象數組中的元素相對應。

      小結
      .NET Remoting是一種在遠程(和本地)計算機上執行代碼簡單有效的機制。只需將代碼封裝到.NET對象中,編寫加載該對象并偵聽請求的程序,然后在客戶端程序中調用Activator.GetObject。如果您的LAN中有一些閑置的計算機,可以利用它們輕松地解決并行問題。只需記住要使用正確的線程同步機制,以防止線程之間發生沖突。



      日韩精品一区二区三区高清