使用.NET Remoting完成并行計算
發(fā)表時間:2023-08-17 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要][簡介]過去,做一個并行計算的試驗要費九牛二虎之力,今天,有了.NET Remoting,我們只需要完成非常少的編程工作,便可以跨多臺計算機輕松進行分布計算。在本文中,Eric Bergman-Te...
[簡介]
過去,做一個并行計算的試驗要費九牛二虎之力,今天,有了.NET Remoting,我們只需要完成非常少的編程工作,便可以跨多臺計算機輕松進行分布計算。在本文中,Eric Bergman-Terrell創(chuàng)建了一個名為Digits of Pi的應(yīng)用程序,它使用并行的多臺計算機以不可思議的精度計算π值。他設(shè)法在12小時內(nèi)完成了10,000位數(shù)的計算,卻只使用了相當(dāng)少的計算資源。這比用一臺計算機單獨完成計算快了300%。
歡迎進入.NET Remoting的奇妙世界!在這篇文章里,您將與我一起,親自動手體驗并行計算的威力。為了方便您更好地理解這篇文章,請首先按照下面的步驟作一番準(zhǔn)備:
1.從附增光盤獲取示例應(yīng)用程序及源代碼。
2.打開Everything.sln解決方案。此解決方案包含運行“Digits of Pi”應(yīng)用程序所需的三個項目(Client、Server和ServerLoader)。還包含一個名為SimpleClient的項目。加載Everything.sln之后,請選擇Build(編譯) Batch Build...(批編譯...)。單擊Select All(全部選定)按鈕,然后單擊Build(編譯)。編譯所有內(nèi)容后,請在本地計算機以及您的LAN中的遠程計算機上安裝該軟件。
3.在本地計算機上,創(chuàng)建一個文件夾并將以下文件復(fù)制到其中:
Server\bin\Release\Plouffe_Bellard.dll
Client\bin\Release\DigitsOfPi.exe
4.在每個遠程計算機和本地計算機上,創(chuàng)建一個文件夾并將以下文件復(fù)制到其中:
Server\bin\Release\Plouffe_Bellard.dll
ServerLoader\bin\Release\ServerLoader.exe
ServerLoader\ServerLoader.exe.config
5.然后運行ServerLoader.exe程序。當(dāng)然,運行ServerLoader和Digits of Pi程序之前,需要在每臺計算機上安裝.NET Framework。
在所有遠程計算機和本地計算機上運行ServerLoader程序后,請運行Digits of Pi程序。單擊Configure...(配置...)(參見圖1),添加本地計算機名和遠程計算機名。如果不確定某臺計算機的名稱,請查看ServerLoader程序,它在表中顯示其計算機名。如果您很幸運地擁有一個多CPU系統(tǒng),您只需為所有CPU輸入一次計算機名。只需在計算機名后鍵入@符號和一個編號。例如,如果您擁有一個名為“Brainiac”的雙CPU系統(tǒng),則鍵入以下計算機名:“Brainiac@1”和“Brainiac@2”。為多CPU系統(tǒng)輸入多個計算機名可以確保所有計算機的CPU都用于計算π值。輸入所有計算機名后,單擊OK(確定)。
指定要計算的位數(shù)(參見圖2)并單擊Calculate(計算)。請從較少的位數(shù)開始,π值小數(shù)點后面的位數(shù)越多,程序所需的時間就越長。
圖3顯示了Digits of Pi程序如何在本地計算機和遠程計算機中分配工作量,它使用TCP/IP端口9000發(fā)送請求并接收結(jié)果。接下來,我們將詳細探討Remoting、Plouffe_Bellard服務(wù)器對象、ServerLoader程序、SimpleClient程序和Digits of Pi程序。
服務(wù)器對象
服務(wù)器對象將計算指定的九位π值。它被命名為Plouffe_Bellard,因為它使用Fabrice Bellard的增強Simon Plouffe算法。雖然存在更快的算法,但Plouffe-Bellard算法非常簡單(少于300行源代碼),它使用少量的內(nèi)存,并且由于九位數(shù)字可以單獨計算,因此更適于并行執(zhí)行。Plouffe_Bellard.CalculatePiDigits方法將計算在指定位置開始的九位π值。例如,CalculatePiDigits(0)從第一位開始返回九位數(shù)字:141592653。CalculatePiDigits(9)從第十位開始返回九位數(shù)字,依此類推。
ServerLoader
ServerLoader程序?qū)⒓虞d服務(wù)器對象,指定通過LAN訪問服務(wù)器對象的協(xié)議和端口,偵聽來自客戶端程序的傳入調(diào)用,處理調(diào)用并返回結(jié)果。特別值得注意的是,所有這些只需一行代碼便可完成,只需通過使用配置文件的路徑調(diào)用RemotingConfiguration.Configure方法。ServerLoader程序?qū)⒓虞d名為ServerLoader.exe.config的配置文件(參見代碼段1)。此配置文件指定以SingleCall模式加載服務(wù)器對象,即每個傳入調(diào)用都由服務(wù)器對象的一個新實例處理。如果服務(wù)器對象以Singleton模式加載,每個傳入調(diào)用都將由同一個實例處理。類型屬性指定服務(wù)器對象的完整類型名稱(包括PB命名空間)及其程序集的名稱。objectUri屬性指定對象的統(tǒng)一資源標(biāo)識符(URI)的端點。<channel>元素指定使用TCP協(xié)議,端口9000訪問服務(wù)器對象。
代碼段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
我創(chuàng)建了一個名為SimpleClient的程序,以說明客戶端程序訪問遠程計算機上的服務(wù)器對象是多么容易。要運行SimpleClient,首先在遠程計算機上運行ServerLoader,然后在本地計算機上運行SimpleClient.exe程序。在Remote Machine(遠程計算機)文本框中輸入遠程計算機的名稱,然后單擊Calculate(計算)按鈕開始計算第一個九位π值。SimpleClient的CalculateButton_Click方法包含客戶端訪問遠程服務(wù)器所需的所有代碼(參見代碼段2)。可以使用由遠程計算機名、協(xié)議(TCP)和端口號(9000)組成的URL訪問遠程服務(wù)器。例如,要訪問我的“Pentium 200”計算機,則URL為“tcp://Pentium 200:9000/ServerLoader/Plouffe_Bellard”。創(chuàng)建URL后,將使用服務(wù)器的類型(Plouffe_Bellard)和URL調(diào)用Activator.GetObject。然后,返回的值被轉(zhuǎn)換為Plouffe_Bellard對象以備使用。調(diào)用其CalculatePiDigits方法時,請求被發(fā)送到遠程計算機上的ServerLoader。然后,服務(wù)器對象計算小數(shù)位。最后,在一個文本框中顯示返回客戶端程序的結(jié)果。
代碼段2:用于訪問遠程服務(wù)器的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更復(fù)雜。SimpleClient僅通過訪問遠程計算機上的服務(wù)器對象來計算前九位π值。而Digits of Pi則同時使用Configure(配置)對話框中指定的遠程計算機和本地計算機(如圖1所示)并行計算用戶指定的小數(shù)位。服務(wù)器對象在單獨的線程中訪問,以便在可能需要很長時間的計算過程中保持Digits of Pi GUI對用戶操作的響應(yīng)性。
Digits of Pi使用數(shù)組將作業(yè)分為九位數(shù)據(jù)塊,將工作量分配到所有可用的計算機上。用戶單擊Calculate(計算)按鈕后,將創(chuàng)建SolutionArray(參見圖4)。SolutionArray為要計算的每組九位π值分配一個SolutionItem元素。服務(wù)器對象計算m_Digit字段指定的九位數(shù)組后,數(shù)位將存儲在m_Results成員中。m_MachineName成員包含運行服務(wù)器的計算機的名稱。存儲計算機名是為了使Digits of Pi能夠顯示每臺計算機計算的小數(shù)總數(shù)(參見圖2)。
為使服務(wù)器對象并行計算,Digits of Pi將為每個服務(wù)器對象創(chuàng)建一個線程并啟動線程計算。然后,必須等待所有線程完成計算后才能顯示最終結(jié)果。WaitHandle對于等待多個線程很有用。Digits of Pi將為每個線程使用一個WaitHandle,以等待所有線程完成計算。
將調(diào)用CalculationThread.Calculate(參見代碼段3)以便為每個服務(wù)器對象創(chuàng)建一個線程。該操作將啟動線程運行,然后返回一個AutoResetEvent(從WaitHandle衍生而來)。每個線程的AutoResetEvent都存儲在一個數(shù)組中,然后數(shù)組被傳遞給WaitHandle.WaitAll。完成線程計算后,將對其AutoResetEvent調(diào)用Set方法。最后一個線程調(diào)用Set方法后,將返回WaitAll調(diào)用,并顯示π的值。
代碼段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中存儲服務(wù)器對象的計算機名,計算指定的九位小數(shù),并將結(jié)果存儲在SolutionItem中。此進程將一直運行,直到所有SolutionItem中都填充了結(jié)果。有關(guān)詳細信息,請參見代碼段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”。有關(guān)多CPU計算機名的解釋,請參見圖1對話框中的文本。
2.知道正確的計算機名后,將其傳遞給RemotePiCalculator.
GetPiCalculator,這樣才可以通過PiCalculator變量訪問該計算機上的服務(wù)器對象。
3. 如果用戶單擊了Cancel(取消)按鈕,將設(shè)置Abort屬性。如果Abort屬性為true,線程將停止計算。
4. 對MF.Invoke的調(diào)用使線程可以安全地更新ListView中的統(tǒng)計數(shù)據(jù)(參見圖2),即使該ListView是由另一個線程創(chuàng)建的。在32位Windows編程中,絕不允許在創(chuàng)建某個控件的線程之外處理該控件。
5. 完成循環(huán)(即計算完指定的所有π位數(shù)或者用戶單擊Cancel [取消]按鈕)后,將調(diào)用線程的AutoResetEvent的Set函數(shù)。
6. 當(dāng)每個線程都調(diào)用其AutoResetEvent的Set函數(shù)后,將返回對WaitHandle.WaitAll的調(diào)用并顯示結(jié)果。
線程同步
如果Digits of Pi的代碼由多個線程同時訪問,可能會有多個地方出現(xiàn)錯誤。例如,如果兩個線程同時調(diào)用SolutionArray.GetNextItem,可能會返回相同的內(nèi)容。這就是在GetNextItem方法中設(shè)置[MethodImpl(MethodImplOptions.Synchronized)]屬性的原因,該屬性可以確保一次只有一個線程調(diào)用該方法。如果方法的每一行代碼都不應(yīng)由多個線程同時訪問,則使方法同步是一個很好的策略。
由于MainForm.Calculate方法只有一行代碼不能同時被多個線程訪問,因此它將在該行代碼之前調(diào)用Monitor.Enter,并在其后調(diào)用Monitor.Exit。如果該行代碼已在其他線程上運行,Monitor.Enter將被阻止。如果整個函數(shù)已實現(xiàn)同步,那么只保護需要防止多個線程訪問的代碼行就可以提高性能。
從System.Windows.Forms.Control派生的對象(例如Button、TextBox、RichTextBox、Label、ListBox、ListView等等)只應(yīng)由創(chuàng)建它們的線程處理。要從非創(chuàng)建線程中安全處理Control衍生對象,請首先將處理代碼放入一個方法,然后為該方法聲明一個代理:
delegate void SetResultsTextDelegate(String Text);
private void SetResultsText(String Text)
{
ResultsRichTextBox.Text = Text;
}
然后使用Form.Invoke間接調(diào)用該方法:
SetResultsTextDelegate SRTD = new
SetResultsTextDelegate(SetResultsText);
Invoke(SRTD, new object[] { "" } );
Invoke方法將從創(chuàng)建它的線程中調(diào)用該方法,它使用的參數(shù)與對象數(shù)組中的元素相對應(yīng)。
小結(jié)
.NET Remoting是一種在遠程(和本地)計算機上執(zhí)行代碼簡單有效的機制。只需將代碼封裝到.NET對象中,編寫加載該對象并偵聽請求的程序,然后在客戶端程序中調(diào)用Activator.GetObject。如果您的LAN中有一些閑置的計算機,可以利用它們輕松地解決并行問題。只需記住要使用正確的線程同步機制,以防止線程之間發(fā)生沖突。