Multithreading s použitím jazyka C a prostředí Win32

Kompilátor Microsoft C/C++ (MSVC) poskytuje podporu pro vytváření vícevláknových aplikací. Zvažte použití více než jednoho vlákna, pokud vaše aplikace potřebuje provádět nákladné operace, které by způsobily, že uživatelské rozhraní přestane reagovat.

S MSVC existuje několik způsobů, jak programovat s více vlákny: Můžete použít C++/WinRT a knihovnu prostředí Windows Runtime, knihovnu Microsoft Foundation Class (MFC), C++/CLI a modul runtime .NET nebo knihovnu runtime jazyka C a rozhraní API Win32. Tento článek se zabývá multithreadingem v jazyce C. Například kód naleznete v části Ukázka vícevláknového programu v jazyce C.

Programy s více vlákny

Vlákno je v podstatě cesta provádění prostřednictvím programu. Je to také nejmenší jednotka provádění, kterou Win32 plánuje. Vlákno se skládá ze zásobníku, stavu registrů procesoru a položky v seznamu spouštění plánovače systému. Každé vlákno sdílí všechny prostředky procesu.

Proces se skládá z jednoho nebo více vláken a kódu, dat a dalších prostředků programu v paměti. Typické programové prostředky jsou otevřené soubory, semaphores a dynamicky přidělená paměť. Program se spustí, když plánovač systému poskytne jeden z jeho vláken řízení provádění. Plánovač určuje, která vlákna se mají spouštět a kdy se mají spustit. Vlákna s nižší prioritou mohou muset čekat, dokud vlákna s vyšší prioritou dokončí své úlohy. Na počítačích s více procesory může plánovač přesouvat jednotlivá vlákna do různých procesorů, aby se vyrovnává zatížení procesoru.

Každé vlákno v procesu funguje nezávisle. Pokud je nezviditelníte, vlákna se spouští jednotlivě a nejsou si vědoma ostatních vláken v procesu. Vlákna sdílející společné prostředky však musí koordinovat svou práci pomocí semaphores nebo jiné metody komunikace mezi procesy. Další informace o synchronizaci vláken naleznete v tématu Zápis vícevláknového programu Win32.

Podpora knihovny pro multithreading

Všechny verze CRT nyní podporují multithreading s výjimkou neblokovaných verzí některých funkcí. Další informace naleznete v tématu Výkon vícevláknových knihoven. Informace o verzích CRT, které jsou k dispozici pro propojení s kódem, naleznete v tématu Funkce knihovny CRT.

Zahrnuté soubory pro multithreading

Standardní crt include files deklaruje funkce knihovny runtime jazyka C při jejich implementaci v knihovnách. Pokud možnosti kompilátoru určují __fastcall nebo __vectorcall konvence volání, kompilátor předpokládá, že všechny funkce by měly být volány pomocí konvence volání registru. Funkce knihovny runtime používají konvenci volání jazyka C a deklarace ve standardních souborech include kompilátoru říkají, aby vygeneroval správné externí odkazy na tyto funkce.

Funkce CRT pro řízení vláken

Všechny programy Win32 mají alespoň jedno vlákno. Jakékoli vlákno může vytvořit další vlákna. Vlákno může dokončit svou práci rychle a poté ukončit nebo může zůstat aktivní po dobu životnosti programu.

Knihovny CRT poskytují následující funkce pro vytváření a ukončení vlákna: _beginthread, _beginthreadex, _endthread a _endthreadex.

_beginthreadex Funkce _beginthread vytvoří nové vlákno a vrátí identifikátor vlákna, pokud je operace úspěšná. Vlákno se ukončí automaticky, pokud se dokončí provádění. Nebo se může ukončit s voláním nebo _endthread_endthreadex.

Poznámka

Pokud voláte rutiny běhu jazyka C z programu vytvořeného pomocí knihovny libcmt.lib, je nutné spustit vlákna pomocí _beginthread funkce nebo _beginthreadex funkce. Nepoužívejte funkce ExitThread Win32 a CreateThread. Použití SuspendThread může vést k zablokování, pokud je více než jedno vlákno blokováno čekáním na pozastavené vlákno dokončit přístup ke struktuře dat za běhu jazyka C.

Funkce _beginthread a _beginthreadex

_beginthreadex Funkce _beginthread vytvoří nové vlákno. Vlákno sdílí segmenty kódu a dat procesu s jinými vlákny v procesu, ale má vlastní jedinečné hodnoty registru, prostor zásobníku a aktuální instrukční adresu. Systém dává každému vláknu čas na procesor, aby se všechna vlákna v procesu mohly spouštět souběžně.

_beginthread a _beginthreadex jsou podobné funkci CreateThread v rozhraní API Win32, ale mají tyto rozdíly:

  • Inicializují určité proměnné knihovny runtime jazyka C. To je důležité jenom v případě, že ve vláknech používáte knihovnu runtime jazyka C.

  • CreateThread pomáhá poskytovat kontrolu nad atributy zabezpečení. Pomocí této funkce můžete spustit vlákno v pozastaveném stavu.

_beginthread a _beginthreadex pokud došlo k chybě, vraťte popisovač novému vláknu, pokud došlo k chybě, nebo kód chyby.

Funkce _endthread a _endthreadex

Funkce _endthread ukončí vlákno _beginthread vytvořené (a podobně _endthreadex ukončí vlákno vytvořené )._beginthreadex Vlákna se po dokončení automaticky ukončí. _endthread a _endthreadex jsou užitečné pro podmíněné ukončení v rámci vlákna. Vlákno vyhrazené ke zpracování komunikace může například ukončit, pokud nemůže získat kontrolu nad komunikačním portem.

Psaní programů s více vlákny pro prostředí Win32

Když píšete program s více vlákny, musíte koordinovat jejich chování a používat prostředky programu. Také se ujistěte, že každé vlákno obdrží vlastní zásobník.

Sdílení běžných prostředků mezi vlákny

Poznámka

Podobnou diskuzi z hlediska MFC naleznete v tématu Multithreading: Programování Tipy a multithreading: Kdy použít synchronizační třídy.

Každé vlákno má vlastní zásobník a vlastní kopii registrů procesoru. Ostatní prostředky, jako jsou soubory, statická data a paměť haldy, jsou sdíleny všemi vlákny v procesu. Vlákna používající tyto běžné prostředky musí být synchronizovaná. Win32 nabízí několik způsobů synchronizace prostředků, včetně semaphores, kritických oddílů, událostí a mutexů.

Pokud ke statickým datům přistupuje více vláken, musí váš program poskytnout možné konflikty prostředků. Zvažte program, ve kterém jedno vlákno aktualizuje statickou datovou strukturu obsahující souřadnice x,y pro položky, které mají být zobrazeny jiným vláknem. Pokud aktualizační vlákno změní souřadnici x a je před změnou souřadnice y, může být vlákno zobrazení naplánováno před aktualizací souřadnice y. Položka by se zobrazila v nesprávném umístění. Tomuto problému se můžete vyhnout pomocí semaphores k řízení přístupu ke struktuře.

Mutex (zkratka pro mutované exclusion) je způsob komunikace mezi vlákny nebo procesy, které se provádějí asynchronně mezi sebou. Tato komunikace se dá použít ke koordinaci aktivit více vláken nebo procesů, obvykle tím, že řídí přístup ke sdílenému prostředku uzamčením a odemknutím prostředku. Aby bylo možné tento problém aktualizace souřadnic x,y vyřešit, nastaví vlákno aktualizace mutex označující, že se datová struktura používá před provedením aktualizace. Po zpracování obou souřadnic by se mutex vymazal. Před aktualizací zobrazení musí vlákno zobrazení počkat, než se mutex vymaže. Tento proces čekání na mutex se často nazývá blokování u mutexu, protože proces je zablokovaný a nemůže pokračovat, dokud mutex vymaže.

Program Bounce.c zobrazený v sample Multithread C Program používá mutex pojmenovaný ScreenMutex ke koordinaci aktualizací obrazovky. Pokaždé, když je jedno z vláken zobrazení připraveno k zápisu na obrazovku, volá WaitForSingleObject se úchytem ScreenMutex a konstantou INFINITE označující, že WaitForSingleObject volání by mělo blokovat na mutex, a ne vypršení časového limitu. Pokud ScreenMutex je jasné, funkce čekání nastaví mutex, aby ostatní vlákna nemohla kolidovat se zobrazením a pokračuje v provádění vlákna. V opačném případě vlákno blokuje, dokud mutex vymaže. Po dokončení aktualizace zobrazení vlákno uvolní mutex voláním ReleaseMutex.

Zobrazení obrazovky a statická data jsou pouze dvěma prostředky vyžadujícími pečlivou správu. Program může mít například více vláken, které přistupují ke stejnému souboru. Vzhledem k tomu, že jiné vlákno mohlo ukazatel souboru přesunout, musí každé vlákno před čtením nebo zápisem obnovit ukazatel souboru. Kromě toho musí každé vlákno zajistit, aby mezi časem, kdy ukazatel umístí, a časem, kdy přistupuje k souboru, nepřepnutá. Tato vlákna by měla použít semaphore ke koordinaci přístupu k souboru závorkou každého přístupu k souborům a WaitForSingleObjectReleaseMutex voláním. Následující příklad kódu znázorňuje tuto techniku:

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

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

Zásobníky vláken

Veškeré výchozí místo zásobníku aplikace je přiděleno prvnímu vláknu provádění, které se označuje jako vlákno 1. V důsledku toho musíte určit, kolik paměti se má přidělit pro samostatný zásobník pro každé další vlákno, které váš program potřebuje. Operační systém v případě potřeby přidělí pro vlákno další prostor zásobníku, ale musíte zadat výchozí hodnotu.

První argument volání _beginthread je ukazatel na BounceProc funkci, která spouští vlákna. Druhý argument určuje výchozí velikost zásobníku pro vlákno. Posledním argumentem je číslo ID, které je předáno BounceProc. BounceProc použije číslo ID k počátečnímu generátoru náhodných čísel a k výběru barevného atributu vlákna a zobrazovaného znaku.

Vlákna, která volají knihovnu runtime jazyka C nebo rozhraní API Win32, musí umožňovat dostatečný prostor zásobníku pro knihovny a funkce rozhraní API, které volají. Funkce C printf vyžaduje více než 500 bajtů prostoru zásobníku a při volání rutin rozhraní API Win32 byste měli mít k dispozici 2K bajtů prostoru zásobníku.

Vzhledem k tomu, že každé vlákno má vlastní zásobník, můžete zabránit potenciálním kolizím datových položek použitím co nejmenších statických dat. Navrhněte program tak, aby používal automatické proměnné zásobníku pro všechna data, která mohou být soukromá pro vlákno. Jediné globální proměnné v programu Bounce.c jsou buď mutexy, nebo proměnné, které se po inicializaci nikdy nemění.

Win32 také poskytuje úložiště TLS (Thread-Local Storage) pro ukládání dat pro jednotlivá vlákna. Další informace najdete v tématu Místní úložiště vláken (TLS).

Obcházení problémových oblastí pomocí programů s více vlákny

Při vytváření, propojování nebo spouštění programu multithread C může dojít k několika problémům. Některé z nejběžnějších problémů jsou popsané v následující tabulce. (Podobnou diskuzi z pohledu MFC najdete v tématu Multithreading: Programování Tipy.)

Problém Pravděpodobná příčina
Zobrazí se okno se zprávou, že váš program způsobil narušení ochrany. Mnoho programovacích chyb Win32 způsobuje porušení ochrany. Běžnou příčinou porušení ochrany je nepřímé přiřazení dat k ukazatelům null. Vzhledem k tomu, že se váš program snaží získat přístup k paměti, která do ní nepatří, je vydáno porušení ochrany.

Snadný způsob, jak zjistit příčinu porušení ochrany, je zkompilovat program s informacemi o ladění a pak ho spustit prostřednictvím ladicího programu v prostředí sady Visual Studio. Když dojde k chybě ochrany, systém Windows přenese řízení do ladicího programu a kurzor se umístí na řádek, který způsobil problém.
Program generuje řadu chyb kompilace a propojení. Mnoha potenciálním problémům můžete eliminovat nastavením úrovně upozornění kompilátoru na jednu z nejvyšších hodnot a zobrazením upozornění. Pomocí možností úrovně 3 nebo 4 upozornění můžete detekovat neúmyslné převody dat, chybějící prototypy funkcí a použití funkcí, které nejsou funkcemi ANSI.

Viz také

Podpora multithreadingu ve starším kódu (Visual C++)
Ukázkový program s více vlákny v jazyce C
Místní úložiště vláken (TLS)
Souběžnost a asynchronní operace s C++/WinRT
Multithreading s použitím jazyka C++ a prostředí MFC