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 tomto kurzu se dozvíte, jak pomocí zobrazení Vlákenoken Paralelní zásobníky ladit vícevláknovou aplikaci. Toto okno vám pomůže pochopit a ověřit chování vícevláknového kódu za běhu.
Zobrazení vláken je podporováno pro C#, C++ a Visual Basic. Vzorový kód je k dispozici pro C# a C++, ale některé odkazy na kód a ilustrace platí jenom pro ukázkový kód jazyka C#.
Pohled na vlákna vám pomůže:
Zobrazení vizualizací zásobníku volání pro více vláken, které poskytují ucelenější obrázek stavu aplikace než okno Zásobník volání, které zobrazuje zásobník volání pro aktuální vlákno.
Pomoc s identifikací problémů, jako jsou blokovaná nebo zablokovaná vlákna
Zásobníky vícevláknových volání
Stejné části zásobníku volání jsou seskupené dohromady, aby se zjednodušila vizualizace složitých aplikací.
Následující konceptuální animace ukazuje, jak se seskupování aplikuje na zásobníky volání. Seskupují se pouze identické segmenty zásobníku volání. Najeďte myší na seskupený zásobník volání, aby bylo vlákno idenitfy.
Přehled ukázkového kódu (C#, C++)
Vzorový kód v tomto návodu je určen pro aplikaci, která simuluje den v životě gorily. Účelem tohoto cvičení je pochopit, jak pomocí zobrazení Vláken okna Paralelní zásobníky ladit vícevláknovou aplikaci.
Ukázka obsahuje příklad zablokování, ke kterému dochází, když na sebe čekají dvě vlákna.
Aby byl zásobník volání intuitivní, ukázková aplikace provede následující postupné kroky:
- Vytvoří objekt představující gorilu.
- Gorilla se vzbudí.
- Gorilla chodí ráno.
- Gorilla najde banány v džungli.
- Gorilla jí.
- Gorilla se zabývá opičím obchodem.
Vytvoření ukázkového projektu
Vytvoření projektu:
Otevřete Visual Studio a vytvořte nový projekt.
Pokud úvodní okno není otevřené, zvolte Soubor>Okno Start.
V okně Start zvolte Nový projekt.
V okně Vytvořit nový projekt zadejte konzolu do vyhledávacího pole. Potom v seznamu Jazyků zvolte C# nebo C++ a pak ze seznamu Platformy zvolte Windows .
Po použití filtrů jazyka a platformy zvolte konzolovou aplikaci pro vybraný jazyk a pak zvolte Další.
Note
Pokud nevidíte správnou šablonu, přejděte na NástrojeZískat nástroje >a funkce..., čímž se otevře instalační program sady Visual Studio. Zvolte úlohu vývoje desktopových aplikací .NET a pak zvolte Upravit.
V okně Konfigurovat nový projekt zadejte název nebo do pole Název projektu použijte výchozí název. Pak zvolte Další.
V případě projektu .NET zvolte buď doporučenou cílovou architekturu, nebo .NET 8, a pak zvolte Vytvořit.
Zobrazí se nový konzolový projekt. Po vytvoření projektu se zobrazí zdrojový soubor.
Otevřete soubor kódu .cs (nebo .cpp) v projektu. Odstraňte jeho obsah a vytvořte prázdný soubor kódu.
Do prázdného souboru kódu vložte následující kód pro vybraný jazyk.
using System.Diagnostics; namespace Multithreaded_Deadlock { class Jungle { public static readonly object tree = new object(); public static readonly object banana_bunch = new object(); public static Barrier barrier = new Barrier(2); public static int FindBananas() { // Lock tree first, then banana lock (tree) { lock (banana_bunch) { Console.WriteLine("Got bananas."); return 0; } } } static void Gorilla_Start(object lockOrderObj) { Debugger.Break(); bool lockTreeFirst = (bool)lockOrderObj; Gorilla koko = new Gorilla(lockTreeFirst); int result = 0; var done = new ManualResetEventSlim(false); Thread t = new Thread(() => { result = koko.WakeUp(); done.Set(); }); t.Start(); done.Wait(); } static void Main(string[] args) { List<Thread> threads = new List<Thread>(); // Start two threads with opposite lock orders threads.Add(new Thread(Gorilla_Start)); threads[0].Start(true); // First gorilla locks tree then banana threads.Add(new Thread(Gorilla_Start)); threads[1].Start(false); // Second gorilla locks banana then tree foreach (var t in threads) { t.Join(); } } } class Gorilla { private readonly bool lockTreeFirst; public Gorilla(bool lockTreeFirst) { this.lockTreeFirst = lockTreeFirst; } public int WakeUp() { int myResult = MorningWalk(); return myResult; } public int MorningWalk() { Debugger.Break(); if (lockTreeFirst) { lock (Jungle.tree) { Jungle.barrier.SignalAndWait(5000); // For thread timing consistency in sample Jungle.FindBananas(); GobbleUpBananas(); } } else { lock (Jungle.banana_bunch) { Jungle.barrier.SignalAndWait(5000); // For thread timing consistency in sample Jungle.FindBananas(); GobbleUpBananas(); } } return 0; } public void GobbleUpBananas() { Console.WriteLine("Trying to gobble up food..."); DoSomeMonkeyBusiness(); } public void DoSomeMonkeyBusiness() { Thread.Sleep(1000); Console.WriteLine("Monkey business done"); } } }V nabídce Soubor vyberte Uložit vše.
V nabídce Sestavení vyberte Sestavit řešení.
Použití zobrazení Vláken okna Paralelní zásobníky
Chcete-li začít ladit:
V nabídce Ladění vyberte Spustit ladění (nebo F5) a počkejte, až se spustí první
Debugger.Break().Note
V jazyce C++ se ladicí program pozastaví v
__debug_break(). Zbývající odkazy na kód a ilustrace v tomto článku jsou určené pro verzi jazyka C#, ale stejné principy ladění platí pro jazyk C++.Stiskněte jednou klávesu F5 a ladicí program se znovu pozastaví na stejném
Debugger.Break()řádku.Tím se pozastaví druhé volání
Gorilla_Start, které se vyskytuje v druhém vlákně.Tip
Ladicí program se rozdělí na kód na základě jednotlivých vláken. To například znamená, že pokud stisknete F5 pro pokračování v provádění a aplikace narazí na další bod přerušení, může se zastavit v kódu na jiném vlákně. Pokud potřebujete toto chování spravovat pro účely ladění, můžete přidat další zarážky, podmíněné zarážky nebo použít funkci Přerušit vše. Více informací o používání podmíněných zarážek najdete v tématu Sledování jednoho vlákna s podmíněnými zarážkami.
Výběrem možnosti Ladit > paralelní zásobníky systému Windows > otevřete okno Paralelní zásobníky a potom v rozevíracím seznamu Zobrazení vyberte Vlákna.
V zobrazení vláken jsou zásobníkový rám a cesta volání aktuálního vlákna zvýrazněny modře. Aktuální umístění vlákna je zobrazeno žlutou šipkou.
Všimněte si označení zásobníku volání pro
2 vlákna . Když jste naposledy stiskli klávesu F5, spustili jste další vlákno. Pro zjednodušení v složitých aplikacích jsou identické zásobníky volání seskupené do jediné vizuální reprezentace. To zjednodušuje potenciálně složité informace, zejména ve scénářích s mnoha vlákny.Během ladění můžete přepnout, jestli se zobrazí externí kód. Pokud chcete tuto funkci přepnout, vyberte nebo zrušte zaškrtnutí políčka Zobrazit externí kód. Pokud zobrazíte externí kód, můžete tento názorný postup použít, ale výsledky se můžou lišit od ilustrací.
Stiskněte znovu klávesu F5 a ladicí program se pozastaví na
Debugger.Break()řádku vMorningWalkmetodě.Okno Paralelních zásobníků zobrazuje umístění aktuálně spuštěného vlákna v metodě
MorningWalk.
Najeďte myší na metodu
MorningWalka získejte informace o dvou vláknech reprezentovaných seskupeným zásobníkem volání.Aktuální vlákno se také zobrazí v seznamu Vlákno na panelu nástrojů Ladění.
Pomocí seznamu vláken můžete přepnout kontext ladicího programu na jiné vlákno. Tím se nezmění aktuálně běžící vlákno, pouze kontext ladícího prostředí.
Případně můžete kontext ladicího programu přepnout tak, že dvakrát kliknete na metodu v zobrazení Vlákna nebo kliknete pravým tlačítkem myši na metodu v zobrazení Vlákna a vyberete Přepínač na Frame>[ID vlákna].
Znovu stiskněte klávesu F5 a ladicí program se pozastaví v
MorningWalkmetodě pro druhé vlákno.
V závislosti na načasování provádění vlákna můžete v tomto okamžiku vidět buď samostatné, nebo seskupené zásobníky volání.
Na předchozím obrázku jsou zásobníky volání pro dvě vlákna částečně seskupené. Identické segmenty zásobníků volání jsou seskupeny a šipky ukazují na segmenty, které jsou oddělené (tj. nejsou identické). Aktuální rámec zásobníku je označen modrým zvýrazněním.
Znovu stiskněte klávesu F5 a dojde k dlouhému zpoždění a pohled Vlákna nezobrazuje žádné informace o zásobníku volání.
Zpoždění je způsobeno zablokováním. V zobrazení Vlákna se nic nezobrazí, protože i když můžou být vlákna blokovaná, v ladicím programu nejste momentálně pozastaveni.
Note
V jazyce C++ se také zobrazí chyba ladění označující, že
abort()se volala.Tip
Tlačítko Přerušit vše je dobrým způsobem, jak získat informace o zásobníku volání, pokud dojde k zablokování nebo jsou v současnosti blokovaná všechna vlákna.
V horní části integrovaného vývojového prostředí na panelu nástrojů Ladění vyberte tlačítko Přerušit vše (ikona pozastavení) nebo použijte kombinaci kláves Ctrl + Alt + Break.
Horní část zásobníku volání v zobrazení Vlákna ukazuje, že
FindBananasje zaseknutý. Instrukční ukazatel veFindBananasje zvlněná zelená šipka, která označuje aktuální kontext ladicího programu, ale zároveň nám říká, že vlákna nejsou aktuálně spuštěna.Note
V jazyce C++ nevidíte užitečné informace a ikony o vzájemném zablokování. Přesto však najdete vlněnou zelenou šipku v
Jungle.FindBananas, naznačuje na místě zablokování.V editoru kódu najdeme ve funkci zvlněnou zelenou šipku
lock. Tato dvě vlákna jsou velockfunkci vFindBananasmetodě blokována.V závislosti na pořadí provádění vlákna se zablokování zobrazí buď v příkazu
lock(tree), nebo v příkazulock(banana_bunch).Volání na
lockzablokuje vlákna v metoděFindBananas. Jedno vlákno čeká, až druhé vlákno uvolní zámek natree, ale toto druhé vlákno čeká, až bude uvolněn zámek nabanana_bunch, než může uvolnit zámek natree. Toto je příklad klasického vzájemného zablokování, ke kterému dochází, když na sebe čekají dvě vlákna.Pokud používáte Copilot, můžete také získat souhrny vláken generované AI, které vám pomůžou identifikovat potenciální zablokování.
Oprava vzorového kódu
Pokud chcete tento kód opravit, vždy získejte více zámků v konzistentním globálním pořadí ve všech vláknech. Zabráníte tak cirkulárním čekáním a eliminujete zablokování.
Pokud chcete zablokování opravit, nahraďte kód
MorningWalknásledujícím kódem.public int MorningWalk() { Debugger.Break(); // Always lock tree first, then banana_bunch lock (Jungle.tree) { Jungle.barrier.SignalAndWait(5000); // OK to remove lock (Jungle.banana_bunch) { Jungle.FindBananas(); GobbleUpBananas(); } } return 0; }Restartujte aplikaci.
Summary
Tento názorný postup ukázal ladicí okno Paralelní zásobníky. Toto okno použijte u skutečných projektů, které používají vícevláknový kód. Můžete prozkoumat paralelní kód napsaný v jazyce C++, C# nebo Visual Basic.