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


Holtpont hibakeresése a Szálak nézet használatával

Ez az oktatóanyag bemutatja, hogyan használhatja a Szálak nézetet a Párhuzamos Veremablakok ablakokban egy többszálú alkalmazás hibakereséséhez. Ez az ablak segít megérteni és ellenőrizni a többszálú kód futásidejű viselkedését.

A Szálak nézet C#, C++ és Visual Basic esetén támogatott. A C# és a C++ kódhoz mintakód tartozik, de a kódhivatkozások és illusztrációk némelyike csak a C# mintakódra vonatkozik.

A Szálak nézete segít Önnek a következőkben:

  • Több szál hívásveremvizualizációinak megtekintése, amely teljesebb képet nyújt az alkalmazás állapotáról, mint a Hívásverem ablak, amely csak az aktuális szál hívásveremét jeleníti meg.

  • Segítség az olyan problémák azonosításához, mint a blokkolt vagy holtpontú szálak.

Többszálú hívásverem

A hívásverem azonos szakaszai csoportosítva vannak az összetett alkalmazások vizualizációjának egyszerűsítése érdekében.

Az alábbi fogalmi animáció bemutatja, hogyan alkalmazza a rendszer a csoportosítást a hívásveremekre. Csak a hívásverem azonos szegmensei vannak csoportosítva. Vigye az egérmutatót egy csoportosított hívásverem fölé, hogy idenitfy a szálakat.

A hívásveremek csoportosításának ábrája.

Mintakód áttekintése (C#, C++)

Az útmutató mintakódja egy olyan alkalmazáshoz készült, amely egy gorilla életének egy napját szimulálja. A gyakorlat célja annak megismerése, hogyan használható a Párhuzamos veremek ablak Szálnézete egy többszálú alkalmazás hibakereséséhez.

A példa egy holtpontra mutat, amely akkor fordul elő, ha két szál várakozik egymásra.

A hívásverem intuitívsá tétele érdekében a mintaalkalmazás a következő szekvenciális lépéseket hajtja végre:

  1. Létrehoz egy gorillát jelképező objektumot.
  2. Gorilla felébred.
  3. Gorilla megy egy reggeli sétára.
  4. Gorilla banánt talál a dzsungelben.
  5. Gorilla eszik.
  6. A gorilla bohóckodik.

A mintaprojekt létrehozása

A projekt létrehozása:

  1. Nyissa meg a Visual Studiót, és hozzon létre egy új projektet.

    Ha a kezdőablak nincs megnyitva, válassza Fájl>Ablak indításalehetőséget.

    A Start ablakban válassza az Új projekt lehetőséget.

    Az Új projekt létrehozása ablakban írja be vagy írja be a keresőmezőbe a konzolt . Ezután válassza a C# vagy a C++ elemet a Nyelv listából, majd válassza a Windowst a Platform listából.

    A nyelv- és platformszűrők alkalmazása után válassza ki a választott nyelv konzolalkalmazását , majd válassza a Tovább gombot.

    Note

    Ha nem látja a megfelelő sablont, lépjen az Eszközök>lekérése eszközök és szolgáltatások lapra, amely megnyitja a Visual Studio Installert. Válassza a .NET asztali fejlesztés munka terhelését, majd válassza a Módosítás lehetőséget.

    Az új projekt konfigurálása ablakban írjon be egy nevet, vagy használja az alapértelmezett nevet a Projektnév mezőbe. Ezután válassza a Következőlehetőséget.

    .NET-projekt esetén válassza az ajánlott cél keretrendszert vagy a .NET 8-at, majd válassza a Létrehozás lehetőséget.

    Megjelenik egy új konzolprojekt. A projekt létrehozása után megjelenik egy forrásfájl.

  2. Nyissa meg a .cs (vagy .cpp) kódfájlt a projektben. Üres kódfájl létrehozásához törölje annak tartalmát.

  3. Illessze be a választott nyelvhez tartozó alábbi kódot az üres kódfájlba.

     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");
             }
         }
     }
    
  4. A Fájl menüben válassza az Összes mentése lehetőséget.

  5. A Build menüben válassza a Megoldás összeállításalehetőséget.

A Párhuzamos verem ablak Szálak nézetének használata

A hibakeresés megkezdéséhez:

  1. A Hibakeresés menüben válassza a Hibakeresés indítása (vagy F5) lehetőséget, és várja meg az első Debugger.Break() találatot.

    Note

    A C++-ban a hibakereső szünetel __debug_break() helyen. A cikkben szereplő további kódhivatkozások és illusztrációk a C#-verzióra vonatkoznak, de ugyanezek a hibakeresési alapelvek érvényesek a C++-ra is.

  2. Nyomja le egyszer az F5 billentyűt, és a hibakereső ismét megáll ugyanazon Debugger.Break() a sorban.

    Ez szünetel a második hívásban Gorilla_Start, amely egy második szálon belül történik.

    Tip

    A hibakereső szálonként törik át a kódot. Ez azt jelenti például, hogy ha az F5 billentyűt lenyomva folytatja a végrehajtást, és az alkalmazás eléri a következő töréspontot, az egy másik szálon lévő kódra törhet. Ha hibakeresési célból kell kezelnie ezt a viselkedést, hozzáadhat további töréspontokat, feltételes töréspontokat, vagy használhatja az Összes megszakítást. További információ a feltételes töréspontok használatáról: Egyetlen szál követése feltételes töréspontokkal.

  3. A Párhuzamos halmok ablak megnyitásához válassza a Hibakeresés > Windows > Párhuzamos Halmok lehetőséget, majd az ablakban a Nézet legördülő listából válassza a Szálak lehetőséget.

    Képernyőkép a Szálak nézetéről a Párhuzamos veremablakokban.

    A Szálak nézetben az aktuális szál veremkerete és hívási útvonala kék színnel van kiemelve. A szál aktuális helyét a sárga nyíl mutatja.

    Figyelje meg, hogy a hívásverem Gorilla_Start címkéje 2 szál. Amikor legutóbb lenyomta az F5 billentyűt, elindított egy másik szálat. Az összetett alkalmazások egyszerűsítése érdekében az azonos hívásveremek egyetlen vizuális ábrázolásba vannak csoportosítva. Ez leegyszerűsíti a potenciálisan összetett információkat, különösen a sok szálat tartalmazó forgatókönyvekben.

    A hibakeresés során beállíthatja, hogy megjelenjen-e külső kód. A funkció váltásához válassza a Külső kód megjelenítése lehetőséget, vagy törölje a jelölését. Ha külső kódot jelenít meg, akkor is használhatja ezt az útmutatót, de az eredmények eltérhetnek az ábráktól.

  4. Nyomja le ismét az F5 billentyűt, és a hibakereső a Debugger.Break() sorban a MorningWalk metódusnál szünetel.

    A Párhuzamos veremek ablak az aktuális végrehajtási szál helyét jeleníti meg a MorningWalk metódusban.

    F5-öt követően a Szálak nézet képernyőképe.

  5. Vigye az egérmutatót a MorningWalk metódus fölé, hogy megkapja a kapcsolt hívásverem által képviselt két szálról szóló információkat.

    Képernyőkép a hívásveremhez társított szálakról.

    Az aktuális szál a Hibakeresés eszköztár Szál listájában is megjelenik.

    Képernyőkép a hibakeresési eszköztár aktuális száláról.

    A szál lista használatával a hibakereső környezetet egy másik szálra válthatja. Ez nem módosítja az aktuális végrehajtási szálat, csak a hibakereső környezetet.

    Másik lehetőségként a hibakereső környezetét úgy is módosíthatja, hogy duplán kattint egy metódusra a Szálak nézetben, vagy kattintson a jobb gombbal egy metódusra a Szálak nézetben, és válassza a Váltás keretre>[szálazonosító] lehetőséget.

  6. Nyomja le ismét az F5 billentyűt, ekkor a hibakereső megáll a második szál MorningWalk metódusánál.

    Képernyőkép a Szálak nézetről a második F5 után.

    A szál végrehajtásának időzítésétől függően ezen a ponton különálló vagy csoportosított hívásveremek jelennek meg.

    Az előző ábrán a két szál hívásveremei részben vannak csoportosítva. A hívásveremek azonos szegmensei csoportosítva vannak, a nyílvonalak pedig az elválasztott (azaz nem azonos) szegmensekre mutatnak. Az aktuális veremkeretet a kék kiemelés jelzi.

  7. Nyomja le ismét az F5 billentyűt, ekkor hosszú késleltetést fog tapasztalni, a Szálak nézet pedig nem jelenít meg hívásverem adatait.

    A késést holtpont okozza. A Szálak nézetben semmi sem jelenik meg, mert bár a szálak blokkolódhatnak, a hibakereső jelenleg nincs szüneteltetve.

    Note

    C++-ban egy hibakeresési hiba is megjelenik, amely jelzi, hogy a abort() hívás történt.

    Tip

    Az Összes megszakítás gomb jó módszer a hívásverem információinak lekérésére, ha holtpont áll fenn, vagy az összes szál jelenleg le van tiltva.

  8. A hibakeresési eszköztár IDE tetején válassza az Összes megszakítás gombot (szüneteltetés ikon), vagy használja a Ctrl+ Alt+ Break billentyűkombinációt.

    Képernyőkép a Szálak nézetéről az Összes megszakítása parancs kiválasztása után.

    A hívásverem felső része a Szálak nézetben azt mutatja, hogy FindBananas holtponton van. A végrehajtási mutató FindBananas egy görbült zöld nyíl, amely az aktuális hibakereső környezetet jelzi, de azt is jelzi, hogy a szálak jelenleg nem futnak.

    Note

    C++-ban nem találhatók meg a hasznos "holtpont-észlelési" információk és ikonok. A görbült zöld nyíl Jungle.FindBananasazonban továbbra is a holtpont helyére utal.

    A kódszerkesztőben megtaláljuk a görbületes zöld nyilat a lock függvényben. A két szál blokkolva van a lock függvényen a FindBananas metódusban.

    Képernyőkép a kódszerkesztőről az Összes megszakítása lehetőség kiválasztása után.

    A szálvégrehajtás sorrendjétől függően a holtpont megjelenik vagy a lock(tree) vagy a lock(banana_bunch) utasításban.

    A lock hívás blokkolja a szálakat a FindBananas metódusban. Az egyik szál arra vár, hogy a másik szál feloldja a zárolást tree , a másik szál azonban arra vár, hogy a zárolást banana_bunch feloldja, mielőtt feloldhatja a zárolást tree. Ez egy példa egy klasszikus holtpontra, amely akkor fordul elő, ha két szál várakozik egymásra.

    A Copilot használata esetén az AI által generált szálösszesítőket is lekérheti a lehetséges holtpontok azonosításához.

    Képernyőkép a Copilot-szál összefoglaló leírásáról.

A mintakód javítása

A kód javításához mindig több zárolást alkalmazzon egy konzisztens, globális sorrendben minden szálon. Ez megakadályozza a körkörös várakozást, és kiküszöböli a holtpontokat.

  1. A holtpont javításához cserélje le a kódot MorningWalk a következő kódra.

    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;
    }
    
  2. Indítsa újra az alkalmazást.

Summary

Ez az útmutató a Párhuzamos verem hibakereső ablakát mutatta be. Használja ezt az ablakot többszálú kódot használó valós projekteken. A C++, C# vagy Visual Basic nyelven írt párhuzamos kódokat megvizsgálhatja.