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


Többszálúság C és Win32 használatával

A Microsoft C/C++ fordító (MSVC) támogatja a többszálú alkalmazások létrehozását. Fontolja meg több szál használatát, ha az alkalmazásnak költséges műveleteket kell végrehajtania, amelyek miatt a felhasználói felület nem válaszol.

Az MSVC-vel többféle módon is programzható több szálon: használhatja a C++/WinRT és a Windows futtatókörnyezeti kódtárat, a Microsoft Foundation Class (MFC) könyvtárat, a C++/CLI-t és a .NET-futtatókörnyezetet, vagy a C futásidejű kódtárat és a Win32 API-t. Ez a cikk a C-ben történő többszálúságról szól. Példakód: Többszálú mintaprogram a C-ben.

Többszálú programok

A szál alapvetően egy programon keresztüli végrehajtás útvonala. A Win32 által ütemezett legkisebb végrehajtási egység is. A szál egy veremből, a CPU-regiszterek állapotából és a rendszerütemező végrehajtási sorából áll. Minden szál megosztja a folyamat összes erőforrását.

A folyamat egy vagy több szálból, valamint egy program kódából, adataiból és egyéb erőforrásaiból áll a memóriában. Tipikus programerőforrások a megnyitott fájlok, szemaforok és dinamikusan lefoglalt memória. A program akkor hajtódik végre, amikor a rendszerütemező végrehajtási vezérlést ad az egyik szálának. Az ütemező határozza meg, hogy mely szálakat és mikor kell futtatni. Előfordulhat, hogy az alacsonyabb prioritású szálaknak várniuk kell, amíg a magasabb prioritású szálak elvégzik a feladataikat. A többprocesszoros gépeken az ütemező az egyes szálakat különböző processzorokba helyezheti át, hogy kiegyensúlyozza a processzorterhelést.

A folyamat minden szála egymástól függetlenül működik. Ha nem teszi őket láthatóvá egymás számára, a szálak egyenként futnak, és nem ismerik a folyamat többi szálát. A közös erőforrásokat megosztó szálaknak azonban szemaphorokkal vagy más folyamatközi kommunikációval kell összehangolniuk a munkájukat. További információ a szálak szinkronizálásáról: Többszálas Win32-program írása.

Többszálúság támogatása könyvtárak számára

A CRT minden verziója támogatja a többszálú feldolgozást, kivéve néhány függvény zárolás nélküli verzióit. További információ: Többszálú kódtárak teljesítménye. A kóddal összekapcsolható CRT-verziókról további információt a CRT-kódtár funkcióiban talál.

Többszálú fájlok belefoglalása

A szabványos CRT fejlécek deklarálják a C futásidejű könyvtár függvényeit, ahogyan implementálva vannak a könyvtárakban. Ha a fordító beállításai megadják a __fastcall vagy __vectorcall hívási konvenciót, a fordító feltételezi, hogy az összes függvényt a regisztrátorhívási konvencióval kell meghívni. A futásidejű kódtárfüggvények a C-hívási konvenciót használják, és a szabványban szereplő deklarációk tartalmazzák a fájlokat, amelyek arra kérik a fordítót, hogy hozzon létre megfelelő külső hivatkozásokat ezekre a függvényekre.

CRT-függvények szálkezeléshez

Minden Win32-program rendelkezik legalább egy szállal. Bármely szál létrehozhat további szálakat. Egy szál gyorsan befejezheti a munkáját, majd leállhat, vagy aktív maradhat a program életében.

A CRT-kódtárak a következő funkciókat biztosítják a szálak létrehozásához és leállításához: _beginthread, _beginthreadex, _endthread és _endthreadex.

A _beginthread függvények _beginthreadex létrehoznak egy új szálat, és visszaadnak egy szálazonosítót, ha a művelet sikeres. A szál automatikusan leáll, ha befejezi a végrehajtást. Vagy leállíthatja magát egy _endthread vagy _endthreadex hívással.

Megjegyzés:

Ha a c futásidejű rutinokat a libcmt.lib programmal létrehozott programból hívja meg, a szálakat a vagy a _beginthread_beginthreadex függvény használatával kell elindítania. Ne használja a Win32 függvényeket ExitThread és CreateThreada . A SuspendThread használata holtponthoz vezethet, ha egynél több szál blokkolva van, és arra vár, hogy a felfüggesztett szál befejezze a C futásidejű adatstruktúrához való hozzáférését.

A _beginthread és _beginthreadex függvények

A _beginthread és _beginthreadex függvények új szálat hoznak létre. A szálak megosztják egy folyamat kódját és adatszegmenseit a folyamat többi szálával, de saját egyedi regisztrációs értékekkel, veremtérrel és aktuális utasításcímmel is rendelkezik. A rendszer időt ad az egyes szálaknak, hogy a folyamat összes szála egyidejűleg végrehajtható legyen.

_beginthread és _beginthreadex hasonlóak a Win32 API CreateThread függvényéhez, de az alábbi különbségekkel rendelkezik:

  • Inicializálnak bizonyos C futásidejű kódtár-változókat. Ez csak akkor fontos, ha a C futásidejű könyvtárat használja a szálakban.

  • CreateThread segít szabályozni a biztonsági attribútumokat. Ezzel a függvénnyel elindíthat egy szálat, amely felfüggesztett állapotban van.

_beginthread és _beginthreadex adnak vissza egy leírót az új szálhoz, ha sikeresek, vagy hibakódot, ha hiba történt.

A _endthread és _endthreadex függvények

A _endthread függvény leállítja az általa _beginthread létrehozott szálat (és hasonlóképpen _endthreadex leállítja a által _beginthreadexlétrehozott szálat). A szálak a befejezéskor automatikusan leállnak. _endthread és _endthreadex hasznos a feltételes megszakításhoz egy szálon belülről. Egy kommunikációs feldolgozásra dedikált szál például kiléphet, ha nem tudja átvenni a kommunikációs port irányítását.

Többszálú Win32-program írása

Ha több szálból álló programot ír, koordinálnia kell a program erőforrásainak viselkedését és használatát. Emellett győződjön meg arról, hogy minden szál saját vermet kap.

Közös erőforrások megosztása szálak között

Megjegyzés:

Az MFC szempontjából hasonló megbeszélésért lásd Multithreading: Programozási tippek és Többszálúság: Mikor érdemes használni a szinkronizálási osztályokat.

Minden szálnak saját vereme és saját másolata van a CPU-regiszterekről. Az egyéb erőforrásokat, például a fájlokat, a statikus adatokat és a halommemóriát a folyamat összes szála megosztja. Az ilyen gyakori erőforrásokat használó szálakat szinkronizálni kell. A Win32 számos módot kínál az erőforrások szinkronizálására, beleértve a szemaforokat, a kritikus szakaszokat, az eseményeket és a mutexeket.

Ha több szál is hozzáfér a statikus adatokhoz, a programnak biztosítania kell a lehetséges erőforrásütközéseket. Fontolja meg azt a programot, amelyben az egyik szál egy x,y koordinátákat tartalmazó statikus adatstruktúrát frissít egy másik szál által megjelenítendő elemekhez. Ha a frissítési szál módosítja az x koordinátát, és az előre ki van iktatva az y koordináta módosítása előtt, előfordulhat, hogy a megjelenítési szál ütemezve lesz az y koordináta frissítése előtt. Az elem nem a megfelelő helyen jelenik meg. Ezt a problémát elkerülheti, ha a szemaphorok használatával szabályozza a struktúra hozzáférését.

A mutex (a mutual exclusion, vagyis kölcsönös kizárás rövidítése) egy olyan eszköz, amely a szálak vagy folyamatok közötti kommunikációt szolgálja, amelyek egymástól aszinkron módon hajtódnak végre. Ez a kommunikáció több szál vagy folyamat tevékenységeinek koordinálására használható, általában úgy, hogy az erőforrás zárolásával és zárolásának feloldásával szabályozza a megosztott erőforráshoz való hozzáférést. Az x,y koordináta-frissítési probléma megoldásához a frissítési szál beállít egy mutexet, amely jelzi, hogy az adatstruktúra használatban van a frissítés végrehajtása előtt. A két koordináta feldolgozása után törli a mutexet. A megjelenítési szálnak meg kell várnia, amíg a mutex törlődik a kijelző frissítése előtt. A mutexre való várakozás ezt a folyamatot gyakran blokkolásnak nevezik a mutexen, mert a folyamat le van tiltva, és nem folytatható, amíg a mutex el nem törlődik.

A Többszálas C mintaprogramban látható Bounce.c program egy elnevezett ScreenMutex mutexet használ a képernyőfrissítések koordinálásához. Minden alkalommal, amikor az egyik megjelenítési szál készen áll arra, hogy a képernyőre írjon, a WaitForSingleObject függvényt hívja a ScreenMutex fogóponttal és az INFINITE állandóval, hogy jelezze: a WaitForSingleObject hívás blokkolja a mutexet, és nem szabad időtúllépnie. Ha ScreenMutex tiszta, a várakozási függvény beállítja a mutexet, hogy más szálak ne zavarhassák a megjelenítést, és folytatja a szál végrehajtását. Ellenkező esetben a szál addig blokkol, amíg a mutex el nem törlődik. Amikor a szál befejezi a megjelenítési frissítést, a hívással ReleaseMutexfeloldja a mutexet.

A képernyőkijelzők és a statikus adatok csak két erőforrást igényelnek gondos felügyelet mellett. Előfordulhat például, hogy a program több szállal is hozzáfér ugyanahhoz a fájlhoz. Mivel előfordulhat, hogy egy másik szál áthelyezte a fájlmutatót, minden szálnak alaphelyzetbe kell állítania a fájlmutatót olvasás vagy írás előtt. Emellett minden szálnak meg kell győződnie arról, hogy a mutató elhelyezése és a fájl elérése között nem kerül megszakításra. Ezeknek a szálaknak szemafor használatával kell koordinálniuk a fájlhoz való hozzáférést, úgy, hogy az egyes fájlhozzáféréseket WaitForSingleObject és ReleaseMutex hívásokkal keretezik. Az alábbi példakód ezt a technikát szemlélteti:

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

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

Szálveremek

Az alkalmazás összes alapértelmezett veremterületét a végrehajtás első szálához rendelik, amelyet 1. szálnak nevezünk. Ennek eredményeképpen meg kell adnia, hogy mennyi memóriát kell lefoglalnia egy külön veremhez minden további szálhoz, amelyre a programnak szüksége van. Az operációs rendszer szükség esetén további veremterületet foglal le a szálhoz, de meg kell adnia egy alapértelmezett értéket.

Az első argumentum a _beginthread hívásban egy mutató a BounceProc függvényre, amely végrehajtja a szálakat. A második argumentum a szál alapértelmezett veremméretét adja meg. Az utolsó argumentum egy azonosítószám, amely a következőnek lesz átadva BounceProc: BounceProc Az azonosítószám használatával veti be a véletlenszerű számgenerátort, és kiválasztja a szál színattribútumát és megjelenítendő karakterét.

A C futásidejű kódtárba vagy a Win32 API-ba irányuló hívásokat kezdeményező szálaknak elegendő veremterületet kell engedélyezniük az általuk hívott kódtár és API-függvények számára. A C printf függvény több mint 500 bájtnyi veremterületet igényel, és a Win32 API-rutinok hívásakor 2K bájtnyi veremterületnek kell rendelkezésre állnia.

Mivel minden szál saját vermet tartalmaz, elkerülheti az adatütközéseket, ha a lehető legkevesebb statikus adatot használja. Úgy tervezheti meg a programot, hogy automatikus veremváltozókat használjon minden olyan adathoz, amely privát lehet egy szálhoz. A Bounce.c program egyetlen globális változója vagy mutex vagy változó, amely az inicializálás után nem változik.

A Win32 szálalapú tárolást (TLS) is biztosít a szálonkénti adatok tárolásához. További információért lásd a szálhoz kötött tárolás (TLS) című részt.

Többszálú programokkal kapcsolatos problémák elkerülése

Többszálú C-program létrehozása, csatolása vagy végrehajtása során számos probléma merülhet fel. A leggyakoribb problémák némelyikét az alábbi táblázatban ismertetjük. (Az MFC szempontjából hasonló vitafórumért lásd : Többszálúság: Programozási tippek.)

Probléma Valószínű ok
Megjelenik egy üzenetmező, amelyen látható, hogy a program védelmi szabálysértést okozott. Számos Win32-beli programozási hiba védelmi szabálysértéseket okoz. A védelmi szabálysértések gyakori oka az adatok nullmutatókhoz való közvetett hozzárendelése. Mivel ez azt eredményezi, hogy a program olyan memóriát próbál elérni, amely nem tartozik hozzá, védelmi szabálysértést ad ki.

A védelmi szabálysértés okának felderítéséhez egyszerűen lefordíthatja a programot hibakeresési adatokkal, majd futtathatja azt a Visual Studio-környezetben található hibakeresőn. A védelmi hiba bekövetkezésekor a Windows átadja a vezérlőt a hibakeresőnek, és a kurzor a problémát okozó vonalon van.
A program számos fordítási és csatolási hibát generál. Számos lehetséges problémát kiküszöbölhet, ha a fordító figyelmeztetési szintjét az egyik legmagasabb értékre állítja, és figyel a figyelmeztető üzenetekre. A 3. vagy a 4. szintű figyelmeztetési szint beállításainak használatával észlelheti a nem szándékos adatátalakításokat, a hiányzó függvény-prototípusokat és a nem ANSI-funkciók használatát.

Lásd még

A régebbi kód többszálú támogatása (Visual C++)
Minta többszálú program C-ben
Szál helyi tárolója (TLS)
Egyidejűség és aszinkron műveletek a C++/WinRT használatával
Többszálúság C++ és MFC használatával