Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
V mnoha případech mohou Parallel.For a Parallel.ForEach poskytnout významné zlepšení výkonu oproti běžným sekvenčním smyčkám. Práce paralelizace smyčky ale představuje složitost, která může vést k problémům, které v sekvenčním kódu nejsou tak běžné nebo vůbec nejsou zjištěny. Toto téma obsahuje několik postupů, které byste se měli vyhnout při psaní paralelních smyček.
Nepředpokládáme, že paralelní je vždy rychlejší
V některých případech může paralelní smyčka běžet pomaleji než její sekvenční ekvivalent. Základním pravidlem je, že paralelní smyčky, které mají málo iterací a rychlé delegáty uživatelů, se pravděpodobně příliš nezrychlí. Vzhledem k tomu, že výkon se týká mnoha faktorů, doporučujeme vždy měřit skutečné výsledky.
Vyhněte se zápisu do umístění sdílené paměti
V sekvenčním kódu není neobvyklé číst ze statických proměnných nebo zapisovat do polí třídy. Kdykoli však k těmto proměnným současně přistupuje více vláken, existuje velký potenciál pro podmínky souběhu. I když můžete použít zámky k synchronizaci přístupu k proměnné, náklady na synchronizaci můžou poškodit výkon. Proto doporučujeme, abyste se co nejvíce vyhnuli nebo omezili přístup ke sdílenému stavu v paralelní smyčce. Nejlepším způsobem, jak to provést, je použít přetížení Parallel.For a Parallel.ForEach, která využívají proměnnou System.Threading.ThreadLocal<T> k uložení vláknově lokálního stavu během provádění cyklu. Další informace najdete v tématu Postupy: Zápis smyčky Parallel.For s využitím vláken místních proměnných a Postupy: Zápis smyčky Parallel.ForEach pomocí oddílů místních proměnných.
Vyhněte se nadměrné paralelizaci
Při používání paralelních smyček vám vznikají režijní náklady na dělení zdrojové kolekce a synchronizaci pracovních vláken. Výhody paralelizace jsou dále omezeny počtem procesorů v počítači. Není možné získat žádné zrychlení spuštěním několika vláken vázaných na výpočetní výkon pouze na jednom procesoru. Proto musíte být opatrní, abyste příliš neparalelizovali smyčku.
Nejběžnějším scénářem, ve kterém může dojít k nadměrné paralelizaci, je ve vnořených smyčkách. Ve většině případů je nejlepší paralelizovat pouze vnější smyčku, pokud neplatí jedna nebo více následujících podmínek:
Vnitřní smyčka je známa tím, že je velmi dlouhá.
Provádíte nákladný výpočet pro každou objednávku. (Operace zobrazená v příkladu není nákladná.)
Cílový systém je známý, že má dostatek procesorů pro zpracování počtu vláken, která budou vytvořena paralelizací zpracování.
Nejlepší způsob, jak určit optimální tvar dotazu, je ve všech případech testovat a měřit.
Vyhněte se volání metod, které nejsou bezpečné pro přístup z více vláken.
Zápis do instančních metod, které nejsou bezpečné pro vlákna, z paralelní smyčky může vést k poškození dat, které může nebo nemusí být v programu neodhaleno. Může také vést k výjimkám. V následujícím příkladu by se více vláken pokusilo volat metodu FileStream.WriteByte současně, což není podporováno třídou.
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)))
Omezení volání metod bezpečných pro přístup z více vláken
Většina statických metod v .NET je thread-safe a může být volána současně z více vláken. I v těchto případech ale může synchronizace vést k významnému zpomalení dotazu.
Poznámka:
Můžete to otestovat sami vložením některých volání WriteLine do dotazů. I když se tato metoda používá v příkladech dokumentace k demonstračním účelům, nepoužívejte ji v paralelních smyčkách, pokud to není nutné.
Mějte na paměti problémy s afinitou vláken
Některé technologie, například interoperabilita modelu COM pro komponenty STA (Single-Threaded Apartment), model Windows Forms a Windows Presentation Foundation (WPF), ukládají omezení spřažení vláken, která vyžadují, aby kód běžel na konkrétním vlákně. Například v model Windows Forms i WPF lze k ovládacímu prvku přistupovat pouze ve vlákně, na kterém byl vytvořen. To znamená, že například nelze aktualizovat ovládací prvek seznamu z paralelního cyklu, pokud nenakonfigurujete plánovač vláken tak, aby plánoval práci pouze na vlákně uživatelského rozhraní. Další informace naleznete v tématu Určení kontextu synchronizace.
Při čekání na delegáty, které volá parallel.Invoke, používejte upozornění.
Za určitých okolností paralelní knihovna úloh vloží úkol, což znamená, že úkol běží na aktuálně běžícím vlákně. (Další informace najdete v tématu Plánovače úloh.) Tato optimalizace výkonu může v určitých případech vést k zablokování. Například dva úkoly můžou spustit stejný kód delegáta, který signalizuje, kdy dojde k události, a pak čeká na signál druhého úkolu. Pokud je druhý úkol vložen ve stejném vlákně jako první a první přejde do stavu Čekání, druhý úkol nebude nikdy schopen signalizovat událost. Chcete-li se takovému výskytu vyhnout, můžete zadat časový limit operace Čekání nebo pomocí explicitních konstruktorů vláken zajistit, aby jedna úloha nemohla blokovat druhou.
Nepředpokládáme, že se iterace forEach, For a ForAll vždy spouští paralelně.
Je důležité mít na paměti, že jednotlivé iterace ve smyčkách For, ForEach nebo ForAll se mohou, ale nemusí spouštět paralelně. Proto byste se měli vyhnout psaní jakéhokoli kódu, který závisí na správnosti paralelního provádění iterací nebo na provádění iterací v libovolném konkrétním pořadí. Například tento kód se pravděpodobně zasekne.
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
V tomto příkladu jedna iterace nastaví událost a všechny ostatní iterace čekají na událost. Žádná z čekajících iterací se nemůže dokončit, dokud se iterace nastavení událostí nedokončí. Je však možné, že čekající iterace zablokují všechna vlákna použitá k provedení paralelní smyčky, ještě než má iterace určená k nastavení události příležitost se vykonat. Výsledkem je vzájemné zablokování – iterace nastavení událostí se nikdy nespustí a čekající iterace se nikdy neprobudí.
Konkrétně by jedna iterace paralelní smyčky nikdy neměla čekat na další iteraci smyčky, aby se dosáhlo pokroku. Pokud se paralelní smyčka rozhodne naplánovat iterace postupně, ale v opačném pořadí, dojde k zablokování.
Vyhněte se provádění paralelních smyček ve vlákně uživatelského rozhraní
Je důležité zachovat odezvu uživatelského rozhraní aplikace. Pokud operace obsahuje dostatek práce na povolení paralelizace, pravděpodobně by neměla být spuštěna ve vlákně uživatelského rozhraní. Místo toho by měla tuto operaci přesměrovat, aby byla spuštěna na vlákně na pozadí. Pokud například chcete použít paralelní smyčku k výpočtu některých dat, která by se pak měla vykreslit do ovládacího prvku uživatelského rozhraní, měli byste zvážit spuštění smyčky v instanci úlohy, nikoli přímo v obslužné rutině události uživatelského rozhraní. Teprve po dokončení základního výpočtu byste měli uživatelské rozhraní zařadit zpět do vlákna uživatelského rozhraní.
Pokud spouštíte paralelní smyčky ve vlákně uživatelského rozhraní, dávejte pozor, abyste se vyhnuli aktualizaci ovládacích prvků uživatelského rozhraní ve smyčce. Pokus o aktualizaci ovládacích prvků uživatelského rozhraní z paralelní smyčky, která se spouští ve vlákně uživatelského rozhraní, může vést k poškození stavu, výjimky, zpožděné aktualizace a dokonce zablokování v závislosti na způsobu vyvolání aktualizace uživatelského rozhraní. V následujícím příkladu paralelní smyčka blokuje vlákno uživatelského rozhraní, na kterém se spouští, dokud nebudou dokončeny všechny iterace. Pokud však iterace smyčky běží na pozadí ve vlákně (jak to může For udělat), volání metody Invoke způsobí odeslání zprávy do vlákna uživatelského rozhraní a zablokuje se, dokud tato zpráva není zpracována. Vzhledem k tomu, že je vlákno uživatelského rozhraní blokováno spuštěním For, zpráva nemůže být nikdy zpracována a vlákno uživatelského rozhraní se zasekne.
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
Následující příklad ukazuje, jak se vyhnout vzájemnému zablokování spuštěním smyčky uvnitř instance úlohy. Vlákno uživatelského rozhraní není zablokované smyčkou a zprávu lze zpracovat.
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