Dela via


Multitrådning med C och Win32

Microsoft C/C++-kompilatorn (MSVC) har stöd för att skapa flertrådsprogram. Överväg att använda mer än en tråd om programmet behöver utföra dyra åtgärder som gör att användargränssnittet inte svarar.

Med MSVC finns det flera sätt att programmera med flera trådar: Du kan använda C++/WinRT och Windows Runtime-biblioteket, MFC-biblioteket (Microsoft Foundation Class), C++/CLI och .NET-körningen eller C-körningsbiblioteket och Win32-API:et. Den här artikeln handlar om multitrådning i C. Exempelkod finns i Exempel på flertrådsprogram i C.

Flertrådsprogram

En tråd är i princip en exekveringsväg genom ett program. Det är också den minsta körningsenheten som Win32-systemet schemalägger. En tråd består av en stack, tillståndet för CPU-registreringarna och en post i körningslistan för systemschemaläggaren. Varje tråd delar alla processens resurser.

En process består av en eller flera trådar och kod, data och andra resurser i ett program i minnet. Typiska programresurser är öppna filer, semaforer och dynamiskt allokerat minne. Ett program exekveras när systemschemaläggaren ger en av sina trådar exekveringskontroll. Schemaläggaren avgör vilka trådar som ska köras och när de ska köras. Trådar med lägre prioritet kan behöva vänta medan trådar med högre prioritet slutför sina uppgifter. På datorer med flera processorer kan schemaläggaren flytta enskilda trådar till olika processorer för att balansera CPU-belastningen.

Varje tråd i en process fungerar oberoende av varandra. Om du inte gör dem synliga för varandra körs trådarna individuellt och känner inte till de andra trådarna i en process. Trådar som delar gemensamma resurser måste dock samordna sitt arbete med hjälp av semaforer eller någon annan metod för kommunikation mellan processer. Mer information om hur du synkroniserar trådar finns i Skriva ett Flertrådat Win32-program.

Biblioteksstöd för multitrådning

Alla versioner av CRT stöder nu multitrådning, med undantag för icke-låsningsversioner av vissa funktioner. Mer information finns i Prestanda för flertrådade bibliotek. Information om vilka versioner av CRT som är tillgängliga för att länka till din kod finns i CRT-biblioteksfunktioner.

Inkludera filer för multitrådning

Standard CRT innehåller filer som deklarerar C-körningsbiblioteksfunktionerna när de implementeras i biblioteken. Om dina kompilatoralternativ anger __fastcall eller __vectorcall anropskonventioner förutsätter kompilatorn att alla funktioner ska anropas med hjälp av registeranropskonventionen. Körtidsbiblioteksfunktionerna använder C-anropskonventionen, och deklarationerna i standard-inkluderingsfilerna instruerar kompilatorn att generera korrekta externa referenser till dessa funktioner.

CRT-funktioner för trådkontroll

Alla Win32-program har minst en tråd. Alla trådar kan skapa ytterligare trådar. En tråd kan slutföra sitt arbete snabbt och sedan avsluta, eller så kan den förbli aktiv under programmets livslängd.

CRT-biblioteken tillhandahåller följande funktioner för att skapa och avsluta trådar: _beginthread, _beginthreadex, _endthread och _endthreadex.

Funktionerna _beginthread och _beginthreadex skapar en ny tråd och returnerar en trådidentifierare om åtgärden lyckas. Tråden avslutas automatiskt om den slutför körningen. Eller så kan den avsluta sig själv med ett anrop till _endthread eller _endthreadex.

Anmärkning

Om du anropar C-körningsrutiner från ett program som skapats med libcmt.lib måste du starta dina trådar med _beginthread funktionen eller _beginthreadex . Använd inte Funktionerna Win32 ExitThread och CreateThread. Användning SuspendThread kan leda till ett dödläge när mer än en tråd blockeras i väntan på att den pausade tråden ska slutföra sin åtkomst till en C-körningsdatastruktur.

Funktionerna _beginthread och _beginthreadex

Funktionerna _beginthread och _beginthreadex skapar en ny tråd. En tråd delar kod- och datasegmenten i en process med andra trådar i processen men har egna unika registervärden, stackutrymme och aktuell instruktionsadress. Systemet ger cpu-tid till varje tråd, så att alla trådar i en process kan köras samtidigt.

_beginthread och _beginthreadex liknar funktionen CreateThread i Win32-API:et men har följande skillnader:

  • De initierar vissa variabler i C-körbiblioteket. Det är viktigt bara om du använder C-körbiblioteket i dina trådar.

  • CreateThread hjälper till att ge kontroll över säkerhetsattribut. Du kan använda den här funktionen för att starta en tråd i pausat tillstånd.

_beginthread och _beginthreadex returnerar ett handtag till en ny tråd om det lyckas eller en felkod om det uppstod ett fel.

Funktionerna _endthread och _endthreadex

Funktionen _endthread avslutar en tråd som skapats av _beginthread (och på liknande sätt _endthreadex avslutar en tråd som skapats av _beginthreadex). Trådar avslutas automatiskt när de är klara. _endthread och _endthreadex är användbara för villkorsstyrd avslutning inifrån en tråd. En tråd som är dedikerad till kommunikationsbearbetning kan till exempel avslutas om den inte kan få kontroll över kommunikationsporten.

Skriva ett multitrådat Win32-program

När du skriver ett program med flera trådar måste du samordna deras beteende och användning av programmets resurser. Kontrollera också att varje tråd tar emot sin egen stack.

Dela gemensamma resurser mellan trådar

Anmärkning

För en liknande diskussion ur MFC-synvinkel, se Multitrådning: Programmeringstips och Multitrådning: När man ska använda synkroniseringsklasserna.

Varje tråd har en egen stack och en egen kopia av CPU-register. Andra resurser, till exempel filer, statiska data och heapminne, delas av alla trådar i processen. Trådar som använder dessa gemensamma resurser måste synkroniseras. Win32 innehåller flera sätt att synkronisera resurser, inklusive semaforer, kritiska avsnitt, händelser och mutexe.

När flera trådar har åtkomst till statiska data måste programmet ange möjliga resurskonflikter. Tänk dig ett program där en tråd uppdaterar en statisk datastruktur som innehåller x,y-koordinater för objekt som ska visas av en annan tråd. Om uppdateringstråden ändrar x-koordinaten och föregrips innan den kan ändra y-koordinaten kan visningstråden schemaläggas innan y-koordinaten uppdateras. Objektet visas på fel plats. Du kan undvika det här problemet med hjälp av semaforer för att styra åtkomsten till strukturen.

Ett mutex, som är en förkortning för mutual exclusion, är ett sätt att kommunicera mellan trådar eller processer som körs asynkront i förhållande till varandra. Den här kommunikationen kan användas för att samordna aktiviteterna i flera trådar eller processer, vanligtvis genom att styra åtkomsten till en delad resurs genom att låsa och låsa upp resursen. För att lösa problemet med x,y-koordinatuppdatering anger uppdateringstråden ett mutex som anger att datastrukturen används innan uppdateringen utförs. Det skulle rensa mutex efter att båda koordinaterna hade bearbetats. Visningstråden måste vänta tills mutexen är klar innan skärmen uppdateras. Den här processen för att vänta på en mutex kallas ofta för blockering på en mutex eftersom processen är blockerad och inte kan fortsätta förrän mutexen har rensats.

Bounce.c-programmet som visas i Sample Multithread C Program använder ett mutex med namnet ScreenMutex för att samordna skärmuppdateringar. Varje gång en av visningstrådarna är redo att skriva till skärmen, anropar den WaitForSingleObject med handtaget till ScreenMutex och konstanten INFINITE för att indikera att anropet WaitForSingleObject ska vänta på mutexen och inte tidbegränsas. Om ScreenMutex är klar, ställer väntefunktionen in mutexen så att andra trådar inte kan störa visningen och fortsätter att exekvera tråden. Annars blockeras tråden tills mutexen frigörs. När tråden har slutfört visningsuppdateringen släpper den mutex genom att anropa ReleaseMutex.

Skärmvisningar och statiska data är bara två av de resurser som kräver noggrann hantering. Programmet kan till exempel ha flera trådar som har åtkomst till samma fil. Eftersom en annan tråd kan ha flyttat filpekaren måste varje tråd återställa filpekaren innan du läser eller skriver. Dessutom måste varje tråd se till att den inte avbryts mellan tidpunkten då den placerar pekaren och tidpunkten då den kommer åt filen. Dessa trådar bör använda en semafor för att samordna åtkomsten till filen genom att omge varje filåtkomst med WaitForSingleObject och ReleaseMutex anrop. Följande kodexempel illustrerar den här tekniken:

HANDLE    hIOMutex = CreateMutex (NULL, FALSE, NULL);

WaitForSingleObject( hIOMutex, INFINITE );
fseek( fp, desired_position, 0L );
fwrite( data, sizeof( data ), 1, fp );
ReleaseMutex( hIOMutex);

Trådstackar

Allt standardstackutrymme för ett program allokeras till den första körningstråden, som kallas tråd 1. Därför måste du ange hur mycket minne som ska allokeras för en separat stack för varje ytterligare tråd som programmet behöver. Operativsystemet allokerar ytterligare stackutrymme för tråden om det behövs, men du måste ange ett standardvärde.

Det första argumentet i anropet _beginthread är en pekare till BounceProc funktionen, som kör trådarna. Det andra argumentet anger standardstackens storlek för tråden. Det sista argumentet är ett ID-nummer som skickas till BounceProc. BounceProc använder ID-numret för att seeda slumptalsgeneratorn och för att välja trådens färgattribut och visningstecken.

Trådar som anropar C-körningsbiblioteket eller Till Win32-API:et måste tillåta tillräckligt med stackutrymme för de biblioteks- och API-funktioner som de anropar. C-funktionen printf kräver mer än 500 byte stackutrymme och du bör ha 2 000 byte stackutrymme tillgängligt när du anropar Win32 API-rutiner.

Eftersom varje tråd har en egen stack kan du undvika potentiella kollisioner över dataobjekt genom att använda så lite statiska data som möjligt. Utforma programmet så att det använder automatiska stackvariabler för alla data som kan vara privata i en tråd. De enda globala variablerna i Bounce.c-programmet är antingen mutexes eller variabler som aldrig ändras när de har initierats.

Win32 tillhandahåller även trådlokal lagring (TLS) för att lagra data per tråd. Mer information finns i Trådlokal lagring (TLS).

Undvika problemområden med flertrådsprogram

Det finns flera problem som kan uppstå när du skapar, länkar eller kör ett C-program med flera trådningar. Några av de vanligaste problemen beskrivs i följande tabell. (En liknande diskussion från MFC-synpunkt finns i Multithreading: Programming Tips.)

Bekymmer Trolig orsak
Du får en meddelanderuta som visar att programmet orsakade en skyddsöverträdelse. Många Win32-programmeringsfel orsakar skyddsöverträdelser. En vanlig orsak till skyddsöverträdelser är den indirekta tilldelningen av data till nullpekare. Eftersom det resulterar i att programmet försöker komma åt minne som inte tillhör det utfärdas en skyddsöverträdelse.

Ett enkelt sätt att identifiera orsaken till en skyddsöverträdelse är att kompilera programmet med felsökningsinformation och sedan köra det via felsökningsprogrammet i Visual Studio-miljön. När skyddsfelet inträffar överför Windows kontrollen till felsökningsprogrammet och markören placeras på den rad som orsakade problemet.
Programmet genererar många kompilerings- och länkfel. Du kan eliminera många potentiella problem genom att ställa in kompilatorns varningsnivå till ett av dess högsta värden och lyssna på varningsmeddelandena. Genom att använda varningsnivån nivå 3 eller nivå 4 kan du identifiera oavsiktliga datakonverteringar, saknade funktionsprototyper och användning av icke-ANSI-funktioner.

Se även

Stöd för multitrådning för äldre kod (Visual C++)
Exempel på flertrådsprogram i C
Trådlokal lagring (TLS)
Samtidighet och asynkrona åtgärder med C++/WinRT-
Multitrådning med C++ och MFC