Megosztás a következőn keresztül:


Lehetséges buktatók az adatok és a tevékenységek párhuzamosságában

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. A hüvelykujj alapvető szabálya, hogy a kevés iterációval és gyors felhasználói meghatalmazottal rendelkező párhuzamos hurkok valószínűleg nem gyorsulnak fel. 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. Ha azonban egyszerre több szál is hozzáfér az ilyen változókhoz, nagy a versenyfeltételek lehetősége. 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 a ciklus végrehajtása során egy változót használ System.Threading.ThreadLocal<T> a szál helyi állapotának Parallel.ForParallel.ForEach tárolására. További információ : How to: Write a Parallel.For Loop with Thread-Local Variables and How to: Write a Parallel.ForEach Loop with Partition-Local Variables.

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 gyorsabb, ha több számítási szálat futtat egyetlen processzoron. Ezért óvatosnak kell lennie, hogy ne párhuzamosítsa túl a hurkot.

A túl párhuzamosítás leggyakoribb forgatókönyve a beágyazott hurkokban van. 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 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 lekérdezés cust.Orderspá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álalapú Széf metódusokra irányuló hívások elkerülése

A nem szálbiztos példányok metódusaiba párhuzamos hurkokból történő írás adatsérüléshez vezethet, amely a programban nem észlelhető vagy nem észlelhető. 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 thread-Széf 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 lakás (STA) összetevői, a Windows Forms és a Windows megjelenítési alaprendszer (WPF) com-interoperabilitása olyan szál affinitási korlátozásokat vezetnek be, amelyek megkövetelik, hogy kód fusson egy adott szálon. 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.

Körültekintően várjon a párhuzamos meghívással meghívott meghatalmazottakban.

Bizonyos körülmények között a tevékenység párhuzamos kódtára beágyazott egy tevékenységet, ami azt jelenti, hogy a tevékenységen fut az éppen futó szálon. (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 egy For, vagy ForAll hurok lehet, ForEach de nem kell végrehajtani párhuzamosan. Ezért ne írjon olyan kódot, amely az iterációk párhuzamos végrehajtásától vagy az iterációk 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 {0} with value of {1}",
                    Thread.CurrentThread.ManagedThreadId, j);
                mre.Set();
            }
            else
            {
                Console.WriteLine("Waiting on {0} with value of {1}",
                    Thread.CurrentThread.ManagedThreadId, 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.

A párhuzamos ciklusok egyik iterációjának nem szabad megvárnia a hurok egy másik iterációját a haladáshoz. 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 ki kell kapcsolnia ezt a műveletet egy háttérszálon való futtatáshoz. 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 azt a felhasználói felületi szálat, amelyen az összes iteráció befejeződik. Ha azonban a hurok iterációja egy háttérszálon fut (ahogy For az is lehetséges), a meghívás meghívása egy üzenetet küld a felhasználói felületi szálnak, és blokkolja az üzenet feldolgozására váró üzeneteket. Mivel a felhasználói felületi szál le van tiltva, az Forüzenet soha nem dolgozható fel, és a felhasználói felületi szál holtpontjai.

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 button1_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

Lásd még