Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Sok esetben jelentős Parallel.ForParallel.ForEach teljesítménybeli javulást biztosíthat a szokásos szekvenciális hurkokkal szemben. A hurok párhuzamosításának munkája azonban összetettséghez vezet, amely olyan problémákhoz vezethet, amelyek szekvenciális kódban nem olyan gyakoriak, vagy egyáltalán nem merülnek fel. Ez a témakör felsorol néhány olyan eljárást, amely elkerülheti a párhuzamos hurkok írását.
Ne feltételezzük, hogy a párhuzamosság mindig gyorsabb
Bizonyos esetekben a párhuzamos hurok lassabban fut, mint a szekvenciális megfelelője. Az általános szabály az, hogy a kevés iterációval és gyors felhasználói függvényekkel rendelkező párhuzamos hurkok valószínűleg nem gyorsulnak fel jelentősen. Mivel azonban a teljesítmény számos tényezőt érint, javasoljuk, hogy mindig mérje a tényleges eredményeket.
Ne írjon megosztott memóriahelyekre
A szekvenciális kódban nem ritka, hogy statikus változókból vagy osztálymezőkből olvasnak vagy írnak. Azonban, ha több szál is egyszerre hozzáfér az ilyen változókhoz, nagy a lehetősége a versenyfeltételeknek. Annak ellenére, hogy zárolásokkal szinkronizálhatja a változóhoz való hozzáférést, a szinkronizálás költsége ronthatja a teljesítményt. Ezért azt javasoljuk, hogy a lehető legnagyobb mértékben kerülje vagy korlátozza a megosztott állapothoz való hozzáférést egy párhuzamos ciklusban. Ennek legjobb módja az, ha Parallel.For és Parallel.ForEach túlterheléseit használja, amelyek egy System.Threading.ThreadLocal<T> változót használnak a szálhelyi állapot tárolására a ciklus végrehajtása során. További információkért lásd: Hogyan írjunk Parallel.For ciklust helyi változókkal és Hogyan írjunk Parallel.ForEach ciklust partíció-lokális változókkal.
Kerülje a túl párhuzamosítást
Párhuzamos hurkok használatával a forrásgyűjtemény particionálásának és a feldolgozószálak szinkronizálásának többletköltségei merülnek fel. A párhuzamosítás előnyeit tovább korlátozza a számítógépen lévő processzorok száma. Nincs gyorsulás, ha több számítás-intenzív szálat futtat egyetlen processzoron. Ezért óvatosnak kell lennie, hogy ne párhuzamosítsa túl a hurkot.
A túlpárhuzamosítás leggyakoribb esete a beágyazott hurkokban fordul elő. A legtöbb esetben a legjobb, ha csak a külső hurkot párhuzamosítja, kivéve, ha az alábbi feltételek közül egy vagy több érvényes:
A belső hurok ismert, hogy nagyon hosszú.
Minden egyes megrendeléshez költséges számítást végez. (A példában látható művelet nem költséges.)
A célrendszerről ismert, hogy elegendő processzorral rendelkezik a feldolgozás párhuzamosításával előállított szálak számának kezeléséhez.
Az optimális lekérdezési alakzat meghatározásának legjobb módja minden esetben a tesztelés és a mérés.
A nem szálbiztos metódusokra irányuló hívások elkerülése
A nem szálbiztos példány metódusainak párhuzamos hurokból történő írása adatsérüléshez vezethet, amely a programban észlelhető vagy észrevétlen maradhat. Ez kivételekhez is vezethet. Az alábbi példában több szál is megpróbálja egyszerre meghívni a FileStream.WriteByte metódust, amelyet az osztály nem támogat.
FileStream fs = File.OpenWrite(path);
byte[] bytes = new Byte[10000000];
// ...
Parallel.For(0, bytes.Length, (i) => fs.WriteByte(bytes[i]));
Dim fs As FileStream = File.OpenWrite(filepath)
Dim bytes() As Byte
ReDim bytes(1000000)
' ...init byte array
Parallel.For(0, bytes.Length, Sub(n) fs.WriteByte(bytes(n)))
A szálbiztos metódusokra irányuló hívások korlátozása
A .NET legtöbb statikus metódusa szálbiztos, és egyszerre több szálból is meghívható. Az érintett szinkronizálás azonban még ezekben az esetekben is jelentős lassulást eredményezhet a lekérdezésben.
Feljegyzés
Ezt saját maga is tesztelheti, ha beszúr néhány hívást WriteLine a lekérdezéseibe. Bár ezt a módszert a dokumentációs példákban bemutató célokra használják, ne használja párhuzamos hurkokban, kivéve, ha szükséges.
Vegye figyelembe a szál affinitási problémáit
Egyes technológiák, például az egyszálas apartman (STA) összetevők COM-interoperabilitása, a Windows Forms és a Windows megjelenítési alaprendszer (WPF) szál affinitási korlátozásokat vezetnek be, amelyek megkövetelik, hogy a kód egy adott szálon fusson. A Windows Formsban és a WPF-ben például egy vezérlő csak azon a szálon érhető el, amelyen létrejött. Ez azt jelenti például, hogy nem frissíthet listavezérlőt párhuzamos ciklusból, hacsak nem konfigurálja a szálütemezőt úgy, hogy csak a felhasználói felület szálán ütemezze a munkát. További információ: Szinkronizálási környezet megadása.
Legyen óvatos, amikor a Parallel.Invoke által hívott delegáltakra vár.
Bizonyos körülmények között a Feladat párhuzamos könyvtára beágyazhat egy feladatot, ami azt jelenti, hogy a feladat a jelenleg futó szálon fut. (További információ: Feladatütemezők.) Ez a teljesítményoptimalizálás bizonyos esetekben holtponthoz vezethet. Előfordulhat például, hogy két tevékenység ugyanazt a delegált kódot futtatja, amely esemény bekövetkezésekor jelzi, majd megvárja, amíg a másik tevékenység jelez. Ha a második tevékenység ugyanazon a szálon van beágyazva, mint az első, és az első várakozási állapotba kerül, a második tevékenység soha nem fogja tudni jelezni az eseményt. Az ilyen események elkerülése érdekében időtúllépést adhat meg a Várakozás művelethez, vagy explicit szálkonstruktorokkal gondoskodhat arról, hogy az egyik tevékenység ne tiltsa le a másikat.
Ne feltételezze, hogy a ForEach, a For és a ForAll Always iterációi párhuzamosan futnak
Fontos szem előtt tartani, hogy az egyes iterációk a For, ForEach vagy ForAll hurkokban végrehajthatók párhuzamosan, de nem szükséges. Ezért kerülje az olyan kód írását, amely a helyes működéshez az iterációk párhuzamos végrehajtásától vagy az iterációk meghatározott sorrendben történő végrehajtásától függ. Ez a kód például valószínűleg holtpontra kerül:
ManualResetEventSlim mre = new ManualResetEventSlim();
Enumerable.Range(0, Environment.ProcessorCount * 100)
.AsParallel()
.ForAll((j) =>
{
if (j == Environment.ProcessorCount)
{
Console.WriteLine($"Set on {Thread.CurrentThread.ManagedThreadId} with value of {j}");
mre.Set();
}
else
{
Console.WriteLine($"Waiting on {Thread.CurrentThread.ManagedThreadId} with value of {j}");
mre.Wait();
}
}); //deadlocks
Dim mres = New ManualResetEventSlim()
Enumerable.Range(0, Environment.ProcessorCount * 100) _
.AsParallel() _
.ForAll(Sub(j)
If j = Environment.ProcessorCount Then
Console.WriteLine("Set on {0} with value of {1}",
Thread.CurrentThread.ManagedThreadId, j)
mres.Set()
Else
Console.WriteLine("Waiting on {0} with value of {1}",
Thread.CurrentThread.ManagedThreadId, j)
mres.Wait()
End If
End Sub) ' deadlocks
Ebben a példában az egyik iteráció beállít egy eseményt, és az összes többi iteráció várakozik az eseményen. A várakozási iterációk egyike sem fejeződhet be, amíg az eseménybeállítási iteráció be nem fejeződik. Előfordulhat azonban, hogy a várakozási iterációk blokkolják a párhuzamos ciklus végrehajtásához használt összes szálat, mielőtt az eseménybeállítási iterációnak lehetősége lett volna végrehajtani. Ez holtpontot eredményez – az eseménybeállítási iteráció soha nem lesz végrehajtva, és a várakozó iterációk soha nem fognak felébredni.
Különösen fontos, hogy a párhuzamos ciklus egy iterációjának sose kelljen várnia egy másik iterációra a haladás érdekében. Ha a párhuzamos hurok úgy dönt, hogy egymás után ütemezi az iterációkat, de ellenkező sorrendben, holtpont jön létre.
Kerülje a párhuzamos hurkok végrehajtását a felhasználói felületen
Fontos, hogy az alkalmazás felhasználói felülete (UI) rugalmas maradjon. Ha egy művelet elegendő munkát tartalmaz a párhuzamosításhoz, akkor valószínűleg nem futtatható a felhasználói felületen. Ehelyett át kell adni ezt a műveletet, hogy egy háttérszálon fusson. Ha például párhuzamos hurkot szeretne használni bizonyos adatok kiszámításához, amelyeket aztán egy felhasználói felület vezérlőjébe kell renderelni, érdemes megfontolnia a ciklus végrehajtását egy feladatpéldányon belül, nem pedig közvetlenül a felhasználói felület eseménykezelőjében. Csak akkor, ha az alapszámítás befejeződött, akkor a felhasználói felület frissítését vissza kell helyeznie a felhasználói felületi szálra.
Ha párhuzamos hurkokat futtat a felhasználói felületen, ügyeljen arra, hogy ne frissítse a felhasználói felület vezérlőit a cikluson belülről. A felhasználói felület vezérlőinek a felhasználói felületi szálon futtatott párhuzamos ciklusból való frissítésének megkísérlése állapotsérüléshez, kivételekhez, késleltetett frissítésekhez és akár holtpontokhoz is vezethet a felhasználói felületi frissítés meghívásától függően. Az alábbi példában a párhuzamos hurok blokkolja a feldolgozó felhasználói felületi szálat, amelyen fut, egészen addig, amíg az összes iteráció be nem fejeződik. Ha azonban a hurok iterációja egy háttérszálon fut (ahogy For esetében lehetséges), az Invoke meghívása egy üzenetet küld a felhasználói felületi szálnak, és blokkol, amíg az üzenet feldolgozásra nem kerül. Mivel a felhasználói felületi szál blokkolva van a For üzenet miatt, az soha nem dolgozható fel, és a felhasználói felületi szál holtpontba kerül.
private void button1_Click(object sender, EventArgs e)
{
Parallel.For(0, N, i =>
{
// do work for i
button1.Invoke((Action)delegate { DisplayProgress(i); });
});
}
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim iterations As Integer = 20
Parallel.For(0, iterations, Sub(x)
Button1.Invoke(Sub()
DisplayProgress(x)
End Sub)
End Sub)
End Sub
Az alábbi példa bemutatja, hogyan kerülheti el a holtpontot a ciklus feladatpéldányon belüli futtatásával. A felhasználói felületi szálat nem blokkolja a hurok, és az üzenet feldolgozható.
private void button2_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
Parallel.For(0, N, i =>
{
// do work for i
button1.Invoke((Action)delegate { DisplayProgress(i); });
})
);
}
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim iterations As Integer = 20
Task.Factory.StartNew(Sub() Parallel.For(0, iterations, Sub(x)
Button1.Invoke(Sub()
DisplayProgress(x)
End Sub)
End Sub))
End Sub