Dela via


Metodtips för hanterad trådning

Multitrådning kräver noggrann programmering. För de flesta uppgifter kan du minska komplexiteten genom att köa begäranden om körning av trådpoolstrådar. Det här avsnittet beskriver svårare situationer, till exempel samordning av arbetet med flera trådar eller hantering av trådar som blockeras.

Kommentar

Från och med .NET Framework 4 tillhandahåller Aktivitetsparallellt bibliotek och PLINQ API:er som minskar en del av komplexiteten och riskerna med programmering med flera trådar. Mer information finns i Parallell programmering i .NET.

Dödlägen och rasförhållanden

Multitrådning löser problem med dataflöde och svarstider, men på så sätt introduceras nya problem: dödlägen och konkurrensförhållanden.

Dödlägen

Ett dödläge uppstår när var och en av två trådar försöker låsa en resurs som den andra redan har låst. Ingen av trådarna kan göra några ytterligare framsteg.

Många metoder i de hanterade trådklasserna ger tidsgränser som hjälper dig att identifiera dödlägen. Följande kod försöker till exempel hämta ett lås på ett objekt med namnet lockObject. Om låset inte hämtas i 300 millisekunder Monitor.TryEnter returnerar false.

If Monitor.TryEnter(lockObject, 300) Then  
    Try  
        ' Place code protected by the Monitor here.  
    Finally  
        Monitor.Exit(lockObject)  
    End Try  
Else  
    ' Code to execute if the attempt times out.  
End If  
if (Monitor.TryEnter(lockObject, 300)) {  
    try {  
        // Place code protected by the Monitor here.  
    }  
    finally {  
        Monitor.Exit(lockObject);  
    }  
}  
else {  
    // Code to execute if the attempt times out.  
}  

Tävlingsförhållanden

Ett konkurrenstillstånd är en bugg som inträffar när resultatet av ett program beror på vilken av två eller flera trådar som når ett visst kodblock först. Att köra programmet många gånger ger olika resultat, och resultatet av en viss körning kan inte förutsägas.

Ett enkelt exempel på ett konkurrenstillstånd är att öka ett fält. Anta att en klass har ett privat statiskt fält (delat i Visual Basic) som ökas varje gång en instans av klassen skapas med hjälp av kod som objCt++; (C#) eller objCt += 1 (Visual Basic). Den här åtgärden kräver att värdet läses in från objCt i ett register, att värdet ökas och lagras i objCt.

I ett flertrådat program kan en tråd som har läst in och ökat värdet föregripas av en annan tråd som utför alla tre stegen. När den första tråden återupptar körningen och lagrar dess värde skriver objCt den över utan att ta hänsyn till det faktum att värdet har ändrats under tiden.

Det här specifika tävlingstillståndet undviks enkelt med hjälp av klassens Interlocked metoder, till exempel Interlocked.Increment. Mer information om andra metoder för att synkronisera data mellan flera trådar finns i Synkronisera data för multitrådning.

Tävlingsförhållanden kan också inträffa när du synkroniserar aktiviteterna i flera trådar. När du skriver en kodrad måste du tänka på vad som kan hända om en tråd föregick körningen av raden (eller innan någon av de enskilda datorinstruktionerna som utgör linjen) och en annan tråd körde över den.

Statiska medlemmar och statiska konstruktorer

En klass initieras inte förrän dess klasskonstruktor (static konstruktor i C#, Shared Sub New i Visual Basic) har körts. För att förhindra körning av kod på en typ som inte initieras blockerar den vanliga språkkörningen alla anrop från andra trådar till static medlemmar i klassen (Shared medlemmar i Visual Basic) tills klasskonstruktorn har körts klart.

Om en klasskonstruktor till exempel startar en ny tråd och trådproceduren anropar en static medlem i klassen, blockerar den nya tråden tills klasskonstruktorn har slutförts.

Detta gäller för alla typer som kan ha en static konstruktor.

Antal processorer

Om det finns flera processorer eller bara en processor som är tillgänglig i ett system kan påverka arkitektur med flera flöden. Mer information finns i Antal processorer.

Använd egenskapen Environment.ProcessorCount för att fastställa antalet processorer som är tillgängliga vid körning.

Allmänna rekommendationer

Tänk på följande riktlinjer när du använder flera trådar:

  • Använd Thread.Abort inte för att avsluta andra trådar. Att anropa Abort en annan tråd liknar att utlösa ett undantag på den tråden, utan att veta vilken punkt tråden har nått i bearbetningen.

  • Använd inte Thread.Suspend och Thread.Resume för att synkronisera aktiviteterna i flera trådar. MutexAnvänd , ManualResetEvent, AutoResetEventoch Monitor.

  • Kontrollera inte körningen av arbetstrådar från huvudprogrammet (till exempel med hjälp av händelser). Utforma i stället programmet så att arbetstrådar ansvarar för att vänta tills arbetet är tillgängligt, köra det och meddela andra delar av programmet när det är klart. Om dina arbetstrådar inte blockeras kan du överväga att använda trådpoolstrådar. Monitor.PulseAll är användbart i situationer där arbetstrådar blockeras.

  • Använd inte typer som låsobjekt. Undvik alltså kod, till exempel lock(typeof(X)) i C# eller SyncLock(GetType(X)) Visual Basic, eller användning av Monitor.Enter med Type objekt. För en viss typ finns det bara en instans av System.Type per programdomän. Om den typ som du låser på är offentlig kan annan kod än din egen låsa den, vilket leder till dödlägen. Ytterligare problem finns i Metodtips för tillförlitlighet.

  • Var försiktig när du låser instanser, till exempel lock(this) i C# eller SyncLock(Me) Visual Basic. Om annan kod i ditt program, utanför typen, låser objektet kan det uppstå dödlägen.

  • Se till att en tråd som har angett en övervakare alltid lämnar den övervakaren, även om ett undantag inträffar när tråden finns i övervakaren. C#-låssatsen och Visual Basic SyncLock-instruktionen anger det här beteendet automatiskt och använder slutligen ett block för att säkerställa att det Monitor.Exit anropas. Om du inte kan se till att Exit anropas kan du ändra designen så att den använder Mutex. En mutex släpps automatiskt när den tråd som för närvarande äger den avslutas.

  • Använd flera trådar för uppgifter som kräver olika resurser och undvik att tilldela flera trådar till en enskild resurs. Till exempel kan alla aktiviteter som involverar I/O dra nytta av att ha en egen tråd, eftersom den tråden kommer att blockeras under I/O-åtgärder och därmed tillåta att andra trådar körs. Användarindata är en annan resurs som drar nytta av en dedikerad tråd. På en dator med en processor samexisterar en uppgift som involverar intensiv beräkning med användarindata och med uppgifter som involverar I/O, men flera beräkningsintensiva uppgifter brottas med varandra.

  • Överväg att använda klassens Interlocked metoder för enkla tillståndsändringar i stället för att använda -instruktionen lock (SyncLock i Visual Basic). -instruktionen lock är ett bra verktyg för generell användning, men Interlocked klassen ger bättre prestanda för uppdateringar som måste vara atomiska. Internt kör den ett enda låsprefix om det inte finns någon konkurrens. I kodgranskningar kan du titta efter kod som den som visas i följande exempel. I det första exemplet ökas en tillståndsvariabel:

    SyncLock lockObject  
        myField += 1  
    End SyncLock  
    
    lock(lockObject)
    {  
        myField++;  
    }  
    

    Du kan förbättra prestandan Increment med hjälp av metoden i stället för -instruktionen lock enligt följande:

    System.Threading.Interlocked.Increment(myField)  
    
    System.Threading.Interlocked.Increment(myField);  
    

    Kommentar

    Add Använd metoden för atomiska steg som är större än 1.

    I det andra exemplet uppdateras endast en referenstypvariabel om det är en null-referens (Nothing i Visual Basic).

    If x Is Nothing Then  
        SyncLock lockObject  
            If x Is Nothing Then  
                x = y  
            End If  
        End SyncLock  
    End If  
    
    if (x == null)  
    {  
        lock (lockObject)  
        {  
            x ??= y;
        }  
    }  
    

    Prestanda kan förbättras med hjälp CompareExchange av metoden i stället, enligt följande:

    System.Threading.Interlocked.CompareExchange(x, y, Nothing)  
    
    System.Threading.Interlocked.CompareExchange(ref x, y, null);  
    

    Kommentar

    Metodöverlagringen CompareExchange<T>(T, T, T) är ett typsäkert alternativ för referenstyper.

Rekommendationer för klassbibliotek

Tänk på följande riktlinjer när du utformar klassbibliotek för flertrådning:

  • Undvik om möjligt behovet av synkronisering. Detta gäller särskilt för kod som används mycket. En algoritm kan till exempel justeras för att tolerera ett konkurrenstillstånd i stället för att eliminera det. Onödig synkronisering minskar prestandan och skapar möjlighet till dödlägen och konkurrensförhållanden.

  • Gör statiska data (Shared i Visual Basic) tråd säkra som standard.

  • Gör inte instansdatatråden säker som standard. Om du lägger till lås för att skapa trådsäker kod minskar prestandan, ökar låskonkurrationen och skapar en möjlighet till dödlägen. I vanliga programmodeller kör endast en tråd i taget användarkod, vilket minimerar behovet av trådsäkerhet. Därför är .NET-klassbiblioteken inte trådsäkra som standard.

  • Undvik att tillhandahålla statiska metoder som ändrar statiskt tillstånd. I vanliga serverscenarier delas statiskt tillstånd mellan begäranden, vilket innebär att flera trådar kan köra koden samtidigt. Detta öppnar möjligheten att tråda buggar. Överväg att använda ett designmönster som kapslar in data i instanser som inte delas mellan begäranden. Om statiska data synkroniseras kan dessutom anrop mellan statiska metoder som ändrar tillstånd leda till dödlägen eller redundant synkronisering, vilket påverkar prestanda negativt.

Se även