Végrehajtási szál (thread; angolból thread - thread) - a feldolgozás legkisebb egysége, amelynek végrehajtását az operációs rendszer kernelje rendelheti hozzá . A szálak és folyamatok megvalósítása operációs rendszerenként eltérő, de a legtöbb esetben a végrehajtási szál egy folyamaton belül található. Egyazon folyamaton belül több végrehajtási szál is létezhet, amelyek megoszthatják az erőforrásokat, például a memóriát , míg a folyamatok nem osztják meg ezeket az erőforrásokat. Különösen a végrehajtási szálak osztoznak egy folyamat utasítássorozatán (kódjában) és kontextusán , a változók értékein (processzorregiszterek és hívásverem ), amelyekkel bármikor rendelkeznek.
Hasonlatként egy folyamat szálai több szakács együttdolgozásához hasonlíthatók. Mindannyian ugyanazt az ételt főzik, ugyanazt a szakácskönyvet olvassák ugyanazzal a recepttel, és követik annak utasításait, és nem feltétlenül mindegyik ugyanazon az oldalon olvassa.
Egyetlen processzoron a többszálú feldolgozás általában időmultiplexeléssel történik (mint a multitasking esetében ): a processzor különböző végrehajtási szálak között vált. Ez a kontextusváltás általában elég gyakran fordul elő ahhoz, hogy a felhasználó egyidejűnek érzékelje a szálak vagy feladatok végrehajtását. A többprocesszoros és többmagos rendszerekben a szálak vagy feladatok ténylegesen futhatnak párhuzamosan is, és minden processzor vagy mag külön szálat vagy feladatot dolgoz fel.
Sok modern operációs rendszer támogatja az időszeletelést a folyamatütemezőből és a többprocesszoros végrehajtási szálakat egyaránt. Az operációs rendszer kernel lehetővé teszi a programozók számára a végrehajtási szálak rendszerhívási felületen keresztül történő vezérlését . Egyes kernel-megvalósítások kernelszálként , míg mások könnyű folyamatként ( LWP ) utalnak rá, amely egy speciális kernelszál, amely ugyanazon az állapoton és adatokon osztozik.
A programok felhasználói térbeli végrehajtási szálakkal rendelkezhetnek, amikor a szálakat időzítőkkel, jelekkel vagy más módszerekkel hozzák létre a végrehajtás megszakításához és időszeletelés létrehozásához egy adott helyzethez ( Ad hoc ).
A végrehajtási szálak abban különböznek a hagyományos többfeladatos operációs rendszer folyamatoktól , hogy:
Az olyan rendszerekről, mint a Windows NT és az OS/2 , állítólag "olcsó" szálak és "drága" folyamatok vannak. Más operációs rendszereken a végrehajtási szálak és a folyamatok közötti különbség nem olyan nagy, kivéve a címtérváltás költségeit, amely asszociatív fordítási puffer használatát foglalja magában .
A többszálú, mint széles körben elterjedt programozási és kódvégrehajtási modell lehetővé teszi több szál futtatását egyetlen folyamaton belül. Ezek a végrehajtási szálak megosztják a folyamat erőforrásait, de önállóan is futhatnak. A többszálú programozási modell kényelmes absztrakciót biztosít a fejlesztőknek a párhuzamos végrehajtásból. A technológia talán legérdekesebb alkalmazása azonban az, amikor egyetlen folyamatra alkalmazzák, ami lehetővé teszi párhuzamos végrehajtását többprocesszoros rendszeren .
A többszálú program ezen előnye lehetővé teszi, hogy gyorsabban fusson több processzorral rendelkező számítógépes rendszereken , többmagos processzorokon vagy gépcsoportokon , mivel a programszálak természetesen alkalmasak a folyamatok valóban párhuzamos végrehajtására. Ebben az esetben a programozónak nagyon óvatosnak kell lennie, hogy elkerülje a versenykörülményeket és más, nem intuitív viselkedést. Az adatok megfelelő manipulálása érdekében a végrehajtási szálaknak gyakran át kell menniük egy találkozási eljáráson, hogy az adatokat a megfelelő sorrendben dolgozzák fel. A végrehajtási szálakhoz mutexekra is szükség lehet (amelyeket gyakran szemaforokkal valósítanak meg ), hogy megakadályozzák a megosztott adatok egyidejű módosítását vagy olvasását a módosítási folyamat során. Az ilyen primitívek hanyag használata holtponthoz vezethet .
A többszálú feldolgozás egy másik felhasználási módja, még egyprocesszoros rendszerek esetében is, hogy az alkalmazás képes válaszolni a bemenetre. Az egyszálú programokban, ha a végrehajtás fő szálát egy régóta futó feladat végrehajtása blokkolja, előfordulhat, hogy az egész alkalmazás lefagyott állapotban van. Ha az ilyen régóta futó feladatokat áthelyezi egy munkaszálra , amely párhuzamosan fut a fő szállal, lehetővé válik, hogy az alkalmazások továbbra is válaszoljanak a felhasználói bevitelre, miközben a feladatok a háttérben futnak. Másrészt a legtöbb esetben a többszálú feldolgozás nem az egyetlen módja annak, hogy egy program érzékeny legyen. Ugyanez érhető el aszinkron I/O-val vagy jelekkel UNIX-ban. [egy]
Az operációs rendszerek kétféle módon ütemezhetik a szálakat:
Az 1990-es évek végéig az asztali processzorok nem rendelkeztek többszálú támogatással, mivel a szálak közötti váltás általában lassabb volt, mint a teljes folyamatkörnyezetváltás . A beágyazott processzorok , amelyek magasabb követelményeket támasztanak a valós idejű viselkedéssel szemben , támogathatják a többszálú feldolgozást azáltal, hogy csökkentik a szálak közötti váltás idejét, esetleg dedikált regiszterfájlokat rendelnek minden egyes végrehajtási szálhoz, ahelyett, hogy elmentenének/visszaállítanának egy közös regiszterfájlt. Az 1990-es évek végén az Intel Pentium 4 processzorral ellátott asztali számítógépekbe is eljutott az a gondolat, hogy egyszerre több szálból hajtsanak végre utasításokat, az úgynevezett szimultán többszálú kezelést, az úgynevezett Hyper-Threadinget . Ezután kizárták az Intel Core és Core 2 architektúra processzoraiból , de később visszaállították a Core i7 architektúrában .
A többszálú kezelés kritikusai azzal érvelnek, hogy a szálak használatának növelése jelentős hátrányokkal jár:
Bár a végrehajtási szálak kis lépésnek tűnnek a szekvenciális számítástechnikától, valójában hatalmas ugrást jelentenek. Feladják a szekvenciális számítástechnika legfontosabb és legvonzóbb tulajdonságait: az érthetőséget, a kiszámíthatóságot és a determinizmust. A végrehajtási szálak, mint a számítási modell, figyelemreméltóan nem determinisztikusak, és ennek a nem-determinizmusnak a csökkentése a programozó feladata lesz. [2]
A folyamat a "legnehezebb" kernel ütemezési egysége. A folyamathoz szükséges saját erőforrásokat az operációs rendszer osztja ki. Az erőforrások közé tartozik a memória, a fájlkezelők, a foglalatok, az eszközfogantyúk és az ablakok. A folyamatok csak olyan explicit módszereken keresztül használnak időmegosztási címteret és erőforrásfájlokat, mint például a fájlleírók és a megosztott memóriaszegmensek öröklése. A folyamatokat általában előre konvertálják többfeladatos végrehajtási módba.
A kernelszálak a kernel ütemezésének "könnyű" egységei közé tartoznak. Minden folyamaton belül van legalább egy végrehajtási kernelszál. Ha egy folyamaton belül több kernel-végrehajtási szál is létezhet, akkor ugyanazon a memória- és erőforrásfájlon osztoznak. Ha az operációs rendszer ütemező végrehajtási folyamata előtérben van, akkor a kernelszálak is előtérben vannak a többfeladatos munkavégzéssel. A kernelszálaknak nincs saját erőforrásuk, kivéve a hívásveremet , a processzor regisztereinek másolatát , beleértve a programszámlálót és a szál helyi memóriáját (ha van). A kernel kijelölhet egy végrehajtási szálat a rendszer minden egyes logikai magjához (mert minden processzor több logikai magra osztja magát, ha támogatja a többszálú feldolgozást, vagy fizikai magonként csak egy logikai magot támogat, ha nem támogatja a többszálú feldolgozást), vagy cserélje ki a blokkolt végrehajtási szálakat. A kernelszálak azonban sokkal több időt vesznek igénybe, mint a felhasználói szálak cseréje.
A végrehajtási szálakat néha a könyvtárak felhasználói terében valósítják meg , ebben az esetben ezeket felhasználói végrehajtási szálaknak nevezik . A kernel nem tud róluk, ezért a felhasználói térben kezelik és ütemezik őket. Egyes megvalósításokban a felhasználói végrehajtási szálak a végrehajtás néhány legfelső kernelszálán alapulnak , hogy kihasználják a többprocesszoros gépek előnyeit (M:N modellek). Ebben a cikkben a "szál" kifejezés alapértelmezés szerint (a "kernel" vagy az "egyéni" minősítő nélkül) a "kernelszálra" utal. A virtuális gépekkel megvalósított felhasználói végrehajtási szálakat "végrehajtási zöld szálaknak" is nevezik. Az egyéni végrehajtási szálak általában gyorsan létrehozhatók és könnyen kezelhetők, de nem tudják kihasználni a többszálú és többfeldolgozási szálakat. Blokkolni tudják, ha az összes hozzá tartozó kernelszál foglalt, még akkor is, ha néhány felhasználói szál futásra készen áll.
A szálak még "könnyebb" ütemezési egységek a kooperatív többfeladatos munkavégzéshez kapcsolódóan : egy futó szálnak kifejezetten "át kell adnia" a végrehajtási jogot más szálaknak, ami sokkal könnyebbé teszi a megvalósításukat, mint a kernelszálak vagy felhasználói szálak megvalósítása. A Fibers ütemezhető, hogy ugyanazon a folyamaton belül bármely végrehajtási szálon fussanak. Ez lehetővé teszi az alkalmazások számára, hogy teljesítménynövekedést érjenek el saját ütemezésük kezelésével, ahelyett, hogy a kernelütemezőre hagyatkoznának (amely esetleg nincs erre konfigurálva). A párhuzamos programozási környezetek, mint például az OpenMP , általában szálakon keresztül valósítják meg feladataikat.
Ugyanabban a folyamatban a végrehajtási szálak ugyanazt a címteret használják. Ez lehetővé teszi az egyidejűleg végrehajtott kódok szoros összekapcsolását és kényelmes adatcserét a folyamatok közötti kommunikáció többletterhelése és bonyolultsága nélkül . Amikor több szál osztozik még egyszerű adatstruktúrákon is, fennáll a versenyhelyzet veszélye, ha egynél több processzor utasításra van szükség az adatok frissítéséhez: előfordulhat, hogy két végrehajtási szál egyszerre próbálja meg frissíteni az adatstruktúrákat, és a végén adatok, amelyek állapota eltér a várttól. A versenykörülmények által okozott hibákat nagyon nehéz reprodukálni és elkülöníteni.
Ennek elkerülése érdekében a végrehajtási alkalmazásprogramozási felületek (API-k) szálai szinkronizálási primitíveket , például mutexet kínálnak , hogy megakadályozzák az adatstruktúrák egyidejű elérését. Egyprocesszoros rendszereken a zárolt mutexhez hozzáférő szálnak le kell állnia, és ezért kontextusváltást kell kezdeményeznie. Többprocesszoros rendszereken egy végrehajtási szál spinlockot kaphat a mutex lekérdezése helyett . Mindkét módszer ronthatja a teljesítményt, és arra kényszerítheti az SMP rendszerek processzorát, hogy versenyezzenek a memóriabuszért, különösen, ha a zárolási modularitás szintje túl magas.
I/O és ütemezésAz egyedi szálak és szálak megvalósítása általában teljes mértékben a felhasználói térben történik. Ennek eredményeként a felhasználói szálak és szálak közötti kontextusváltás ugyanabban a folyamatban nagyon hatékony, mivel egyáltalán nem igényel interakciót a kernellel. A kontextusváltást helyileg úgy hajtják végre, hogy elmentik a futó felhasználói szál vagy szál által használt processzorregisztereket, majd betöltik az új végrehajtáshoz szükséges regisztereket. Mivel az ütemezés a felhasználói térben történik, az ütemezési házirend könnyen szabható egy adott program követelményeihez.
A felhasználói szálak (szemben a kernelszálakkal) és szálak rendszerhívási zárainak használatának azonban megvannak a maga problémái. Ha egy felhasználói szál vagy szál rendszerhívást hajt végre, a folyamatban lévő többi szál és szál nem futhat, amíg a feldolgozás be nem fejeződik. Egy ilyen probléma tipikus példája az I / O műveletek teljesítményével kapcsolatos. A legtöbb program az I/O szinkron végrehajtására készült. Amikor egy I/O-t kezdeményez, rendszerhívás történik, és az nem tér vissza, amíg be nem fejeződik. Időközben az egész folyamatot blokkolja a kernel, és nem futhat, ami működésképtelenné teszi a folyamat többi felhasználói szálát és szálát.
A probléma általános megoldása az, hogy külön I/O API-t biztosítanak, amely belső, nem blokkoló I/O-t használó szinkron interfészt valósít meg, és egy másik felhasználói szálat vagy szálat indítanak el az I/O feldolgozása közben. Hasonló megoldások biztosíthatók a rendszerhívások blokkolására. Ezenkívül a program úgy írható, hogy elkerülje a szinkron I/O vagy más blokkoló rendszerhívásokat.
A SunOS 4.x bevezette az úgynevezett " könnyű folyamatokat " vagy LWP -ket . A NetBSD 2.x + és a DragonFly BSD az LWP-t kernelszálként valósította meg (1:1 modell). A SunOS 5.2 és a SunOS 5.8, valamint a NetBSD 2 és a NetBSD 4 kétszintű modellt valósított meg, kernelszálonként egy vagy több felhasználói szálat használva (M:N modell). A SunOS 5.9 és újabb, valamint a NetBSD 5 megszüntette a felhasználói szálak támogatását, és visszatért az 1:1-es modellhez. [3] A FreeBSD 5 az M:N modellt valósította meg. A FreeBSD 6 támogatja az 1:1 és az M:N modelleket, és a felhasználó kiválaszthatja, hogy melyiket használja az adott programban az /etc/libmap.conf segítségével. A FreeBSD 7-es verziójában az 1:1-es modell lett az alapértelmezett, a FreeBSD 8-as és újabb verzióiban pedig az M:N modell egyáltalán nem támogatott.
A kernelszálak használata leegyszerűsíti a felhasználói kódot azáltal, hogy a többszálú kezelés bonyolultabb aspektusait áthelyezi a kernelbe. A programnak nem kell ütemeznie a végrehajtási szálakat és az explicit processzorrögzítéseket. A felhasználói kód az ismert eljárási stílusban írható, beleértve az API-hívások blokkolását, anélkül, hogy megtagadnák más szálak hozzáférését a processzorhoz. A kernelszálak azonban bármikor kontextusváltást okozhatnak a végrehajtási szálak között, és ezáltal felfedhetnek olyan verseny- és párhuzamossági hibákat, amelyek esetleg nem fordulnak elő. SMP rendszereken ez még súlyosabb, mivel a kernelszálak szó szerint párhuzamosan futhatnak különböző processzorokon.
A felhasználó által az 1-1 modellben létrehozott végrehajtási szálak elküldhető kernel entitásoknak felelnek meg. Ez a szálfűzés lehető legegyszerűbb megvalósítása. A Windows API a kezdetektől ezt a megközelítést alkalmazta. Linuxon a szokásos C-könyvtár valósítja meg ezt a megközelítést ( a POSIX Thread Library -n keresztül, a régebbi verziókban pedig a LinuxThreads -en keresztül ). Ugyanezt a megközelítést használja a Solaris OS , a NetBSD és a FreeBSD .
Az N:1 modell feltételezi, hogy az összes felhasználói szintű végrehajtási szál egyetlen kernelszintű ütemezési entitáshoz van leképezve, és a kernel semmit sem tud az alkalmazásszálak összetételéről. Ezzel a megközelítéssel a kontextusváltás nagyon gyorsan elvégezhető, ráadásul még egyszerű, többszálas rendszermagokon is megvalósítható. Egyik fő hátránya azonban, hogy nem tudja kihasználni a hardveres gyorsítás előnyeit többszálú processzorokon vagy többprocesszoros számítógépeken, mert egy adott időpontban csak egy szál végrehajtása ütemezhető. Ezt a modellt a GNU Portable Threads használják.
Az M:N modellben néhány M számú alkalmazási szál van leképezve néhány N számú kernel entitásra vagy "virtuális processzorra". A modell kompromisszum a kernel szintű ("1:1") és a felhasználói szintű ("N:1") modell között. Általánosságban elmondható, hogy az "M:N" rendszerszál-fűzés megvalósítása bonyolultabb, mint a kernel vagy a felhasználói szálak végrehajtása, mivel sem a kernel, sem a felhasználói terület nem igényel kódmódosítást. Egy M:N megvalósításban a szálkönyvtár felelős a felhasználói szálak végrehajtásának ütemezéséért az elérhető ütemezési entitásokon. Ugyanakkor a szálkontextus váltás nagyon gyorsan megtörténik, mivel a modell elkerüli a rendszerhívásokat. Azonban nő a bonyolultság és a prioritási inverziók valószínűsége, valamint a nem optimális ütemezés a felhasználói ütemező és a kernelütemező közötti kiterjedt (és költséges) koordináció nélkül.
A szálaknak sok különböző, inkompatibilis megvalósítása létezik. Ezek magukban foglalják a kernel szintű megvalósításokat és a felhasználói szintű megvalósításokat is. Leggyakrabban többé-kevésbé szorosan követik a POSIX Threads interfész szabványt .
A Fibers az operációs rendszer támogatása nélkül is megvalósítható, bár egyes operációs rendszerek és könyvtárak kifejezetten támogatják őket.
Sok programozási nyelv eltérően támogatja a szálakat. A legtöbb C és C++ implementáció (a C++11 szabvány előtt) nem nyújt közvetlen támogatást maguknak a szálaknak, de hozzáférést biztosít az operációs rendszer által biztosított szálakhoz API -n keresztül . Egyes magasabb szintű (általában többplatformos) programozási nyelvek, mint például a Java , a Python és a .NET , a szálak fűzését absztrakt, platformspecifikus módon biztosítják a fejlesztő számára, amely különbözik a szálak futásidejű megvalósításától. Számos más programozási nyelv is megpróbálja teljesen elvonatkoztatni a párhuzamosság és a szálfűzés fogalmát a fejlesztőtől ( Cilk , OpenMP , MPI ...). Egyes nyelveket kifejezetten a párhuzamosságra terveztek (Ateji PX, CUDA ).
Egyes értelmező programozási nyelvek, mint például a Ruby és a CPython (a Python egyik implementációja), támogatják a szálakat, de van egy korlátozásuk Global Interpreter Lock (GIL) néven. A GIL egy értelmező által végrehajtott kivételmutex, amely megakadályozhatja, hogy az interpreter egyszerre két vagy több szálban értelmezze az alkalmazáskódot, hatékonyan korlátozva a párhuzamosságot a többmagos rendszereken (főleg a processzorhoz kötött szálaknál, nem a hálózathoz kötött szálakban). ).
Az operációs rendszerek szempontjai | |||||
---|---|---|---|---|---|
| |||||
Típusok |
| ||||
Sejtmag |
| ||||
Folyamatmenedzsment _ |
| ||||
Memóriakezelés és címzés | |||||
Betöltési és inicializálási eszközök | |||||
Héj | |||||
Egyéb | |||||
Kategória Wikimedia Commons Wikikönyvek Wikiszótár |