使用.NET Remoting完成并行計算
發表時間:2023-08-17 來源:明輝站整理相關軟件相關文章人氣:
[摘要][簡介]過去,做一個并行計算的試驗要費九牛二虎之力,今天,有了.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中有一些閑置的計算機,可以利用它們輕松地解決并行問題。只需記住要使用正確的線程同步機制,以防止線程之間發生沖突。