Lock (informatika)
Az informatikában a lock, zár vagy mutex szinkronizációs primitív, ami többszálú környezetben szabályozza a megadott erőforráshoz való hozzáférést. A zár a kölcsönös kizárás érvényesítésére szolgál.
Típusai
A zár többnyire felügyelő zár, ami elvárja a szálak együttműködését. Egyes rendszerek kötelező zárakat is megvalósítanak, amik kivételt váltanak ki abban a szálban, ami az objektumhoz annak zárolt állapotában próbálnak hozzáférni.
A zár legegyszerűbb típusa a bináris szemafor. Kizárólagos hozzáférést biztosít a zárolt adathoz. Vannak sémák, amelyek lehetővé teszik a hozzáférést egyszerre több olvasó számára. További példák a kizáró, a kizárást lehetővé tevő és az upgrade-elhető.
Egy másik osztályozás azt veszi figyelembe, hogy mi történik, ha a zár megállít egy szálat. A legtöbb zár blokkolja a szál végrehajtását, ezért a szálnak várnia kell a futás folytatásával, amíg meg nem szerzi a zárat. Ez megvalósulhat úgy, hogy a szál újra és újra lekérdi, hogy megkapja-e a zárat. Ez akkor hatékony, ha a zárolás rövid ideig tart, mert az ütemezőnek nem kell az alvó száltól elvennie a vezérlést és odaadni egy másik szálnak, tehát adminisztrációt lehet spórolni. Viszont ha a zárolás hosszú, vagy a szál folytatása függ a zároló szál végrehajtásától, akkor nem jó ötlet.
Támogatása
A zárak hardveres támogatást igényelnek ahhoz, hogy hatékonyan lehessen őket használni. Ez rendszerint egy vagy több atoni utasítás formájában valósul meg ("test-and-set", "fetch-and-add" vagy "compare-and-swap"). Ezek lehetővé teszik, hogy atomi lépésben lehessen ellenőrizni a zárat, és ha szabad, akkor lefoglalni.
Egyprocesszoros architektúrákon van lehetőség megszakíthatatlan utasítássorozatok definiálására. Ezt speciális utasításokkal vagy prefixekkel teszik lehetővé, ami viszont többprocesszoros architektúrákon nem áll rendelkezésre. Egy ilyen környezetben bonyolult a zárolás hardveres és szoftveres megvalósítása, és fontos szinkronizációs következményekkel jár.
Az atomiság a párhuzamosság miatt fontos, mivel közben az ütemező közben elveheti a vezérlést a száltól, és egy másik szál is elkezdheti az utasítássorozat végrehajtását, ami bajt okozhat. Például tekintsük a következő C kódot:
if(lock == 0) {
// lock free, set it
lock = myPID;
}
Ez a példa nem garantálja a zár megszerzését, hiszen egyszerre több szál is tesztelheti a feltételt, és megpróbálhatja beállítani az azonosítót, nem tudva a másikról. Dekker és Peterson algoritmusai segítenek áthidalni azt a problémát, amit az atomi zároló művelet hiánya vet fel.
A zárak nem elég körültekintő használata holtpontot vagy livelockot okozhat. Különböző stratégiák vannak ezek elkerülésére vagy feloldására, akár tervezési, akár futásidőben. A leggyakoribb módszer az, hogy csak bizonyos sorrendben lehet lefoglalni az erőforrásokat.
Vannak nyelvek, amelyek beépítetten tartalmaznak zárolást. Az alábbi példa C#-ban:
class Account { // this is a monitor of an account
long val = 0;
object thisLock = new object();
public void deposit(const long x) {
lock(thisLock) { // only one thread at a time may execute this statement
val += x;
}
}
public void withdraw(const long x) {
lock(thisLock) { // only one thread at a time may execute this statement
val -= x;
}
}
}
A lock(this) problémát okozhat, ha a példány publikus.[1]
A Javához hasonlóan C#-ban is lehet teljes metódusokat szinkronizáln a MethodImplOptions szinkronizációs attributum használatával.[2][3]
[MethodImpl(MethodImplOptions.Synchronized)]
public void someMethod() {
// do stuff
}
Szemcsézettség
További fogalmak:
- zárolási adminisztráció: a zároláshoz használt extra műveletek és memória, például a zár létrehozásához és megszüntetéséhez, kezeléséhez használt idő. Minél több zárolást tartalmaz a program, annál több időt tölt a zárak kezelésével.
- kontentív zárolás: egy szál milyen gyakran fut bele olyan zárba, amit egy másik szál fog. Minél finomabban szemcsézett a zárolás, annál ritkábban történik ez meg.
- holtpont: egy olyan helyzet, ahol az összes szál vár legalább egy másikra. Beavatkozás nélkül ez a helyzet nem oldódik meg, örökké tartana, és a program örökre lefagyna.
Az adminisztráció és a kontentív működés között cserekapcsolat van. Azt mondjuk, hogy a zárak durván szemcsézettek, ha nagy kódszakaszokat és egész adatszerkezeteket zárolnak. Ha a zárak finoman szemcsézettek, akkor rövid kódszakaszokat, és az adatszerkezetekben egyes részeket lehet zárolni; sőt, például egy faszerkezetben keresve az egyes részfák külön-külön zárolhatók, így a nagyobb részfa zára a kisebb részfa zárának megszerzése után feloldható. Ebben az esetben több az adminisztráció. A durva szemcsézettség problémája viszont az, hogy kevéssé kontentív, a szálaknak sokat kell várakozniuk. Nem célszerű annyira finomítani a zárolást, hogy összetartozó objektumok zárát külön kelljen megszerezni, mert ez szükségtelen függőségeket vezet be a programba, és holtpontot okozhat.
Adatszerkezetekben és adatbázisokban kisebb-nagyobb elemek is zárolhatók igény szerint. Egész táblák zárolása kevés, adatrekordok és részeik zárolása sok szál esetén jobb.
Adatbázisok
Az adatbázisok zárolásának célja a tranzakciók szinkronizálása. Összefésüléses esetben kétfázisú zárak biztosítják, hogy a konkurrensen futó tranzakciók végrehajtásának eredménye megegyezzen a tranzakciók bizonyos sorrendű futásával. Mellékhatásként azonban előfordulhat holtpont. A holtpont megelőzhető azzal, hogy a tranzakciók között zárolási sorrendet állítunk fel, vagy várakozási zárakkal deríthetők fel. Egy alternatíva a teljesen rendezett globális időpontok használata.
Különböző mechanizmusok támogatják az adatbázis konkurrens használatát; a cél az, hogy megelőzzék a piszkos adatolvasást vagy a frissítések elvesztését.
- Pesszimista zárolás: Az olvasó kizárólagos zárat tesz az olvasott rekordra, hogy addig ne manipulálhassák mások. Ennek következményeként kevésbé lehet hozzáférni, aminek az a hátránya, hogyha sokáig tart a zárolás, akkor a műveletek lelassulnak, ami frusztrálja a felhasználókat.
- Optimista zárolás: Egyszerre többen férhetnek hozzá az adatbázis egyes részeihez. A rendszer másolatot készít az először olvasott adatokról. Ha valaki frissíteni akar egy rekordot, akkor az alkalmazás meghatározza, hogy más megváltoztatta-e a rekordot az utolsó olvasás óta. Ha igen, akkor a rendszer nem engedi a módosítást, és a felhasználónak újra kell kezdenie a tranzakciót. A szükséges zárolások számának csökkentésével javítja a performanciát, így az adatbázis szerver terhelését is. Akkor hatékony, ha csak ritkán kell frissíteni, de a frissítések elveszhetnek. Ha a frissítések nem olyan ritkák, akkor nem hatékony, mert gyakran kell újra elküldeni a kérést, ami szintén frusztrálja a felhasználókat, akik lehet, hogy inkább nem próbálkoznak többször.
A pesszimista zárolás hatékony, ha: egyszerre sok kérés érkezik, vagy a zárolás gyorsabb, mint a tranzakciók visszagörgetése. Elvárja, hogy a zárolások rövid ideig tartsanak, továbbá azt, hogy a kapcsolat ne szakadjon meg a felhasználók és az adatbázis között. Nem skálázódik a feldolgozott adatmenyiséggel arányosan, mert a zárolások hosszabb ideig tarthatnak. Webalkalmazások számára nem a legjobb.
Az optimista zárolás hatékony, ha:[4] egyszerre kevés a kérés, vagy az adatokat sokkal inkább olvassák, mint írják. A .NET kiterjedten használja, mobil és nem csatlakozó alkalmazásokban, ahol a hosszas zárolás megengedhetetlen. Ezekben nem lehet feltételezni az állandó kapcsolatot az adatbázissal sem, amit a pesszimista zárolás megkövetelne.
Hátrányok
A zárolásos adatvédelem és szinkronizáció több hátránnyal jár:
- Várakozás: a szálaknak néha várniuk kell. Ha a zároló szál blokkolódik, akkor sokáig kell várni, ha pedig elveszti a kapcsolatot az adatbázissal, vagy holtpontba fut, akkor az adathoz sosem lehet hozzáférni, hacsak nem lép valaki közbe.
- Adminisztrációs költség: a zárolás lelassítja a hozzáférést az adatokhoz, még akkor is, ha a várakozás ritka.
- Hibakeresés nehezebb: a zárolással összefüggő hibák nehezen vehetők észre és reprodukáltak, mert függenek az időzítéstől, ahogy a holtpont is.
- Instabilitás: a szemcsézettség optimális mérete függ a konkrét problémától, és a hozzáférés gyakoriságától. Érzékeny a tervezésre, megvalósításra, még az alacsony szintű architekturális változásoktól is. Az alkalmazás életciklusa alatt sokat változhat, ami felveti a módosíthatóság kérdését.
- Komponálhatóság: a zárak kombinálása csak bonyolult módon és merev szabályokhoz alkalmazkodva valósítható meg. Tehát bonyolult dolog lehetővé tenni azt, hogy egy tranzakció töröljön egy X itemet az A táblából, és ugyanezt az elemet beszúrja a B táblába.
- A prioritás megfordítása: egy sok adatot zároló alacsonyabb prioritású szál visszatarthat egy magasabb prioritású szálat, ha sorra jönnek a kettő közötti prioritású szálak. Ez kiküszöbölhető a prioritás öröklésével, vagy a prioritástető használatával. Ez a holtpontot is megelőzheti.
- Konvoj: a szálak mind egy szálra várnak, mert nekik is szükségük lenne arra az adatra, amit az a szál zárolt.
Vannak más módszerek is, például a kürtők vagy a szerializációs tokenek, amelyek kiküszöbölik a holtpont lehetőségét. Az alternatívák közé tartoznak a nem blokkoló programozási módszerek, mint a tranzakciós memória és a zárolásmentes technikák. Ezek azonban gyakran megkövetelik, hogy a zárolás alacsonyabb szinten implementálva legyen. Ezzel a fenti problémák nem kerülhetők el, csak a felső szinten nem kell zárolni.
A legtöbb esetben a zárolás konkrét mechanizmusa függ attól, hogy a processzor nyújt metódust az atomi utasítássorozat szinkronizációjára. Például ha egy csővezetékből itemeket törölnek, akkor fel kell függeszteni a többi műveletet, ami konkurrensen fut, és ehhez a csővezetékhez hozzáadna vagy elvenne elemeket. Így az alkalmazás robusztusabb lehet, ha felismeri az operációs rendszer korlátait, és képes felismerni a lehetetlen kéréseket.
A komponálhatóság hiánya
A zárolás egy fontos problémája az, hogy a zárak nem komponálhatók; nehéz komponálni úgy a modulokat, hogy nem nyúlunk beléjük, vagy legalábbis nem ismerjük a tartalmukat. Simon Peyton Jones, a tranzakcionális memória egyik szószólója a következő példát adja egy banki alkalmazásra: Tervezzük meg az Account (Számla) osztályt úgy, hogy lehessen pénzt betenni, kivenni, és átutalni.[5] Az első rész megoldása:
class Account: member balance : Integer member mutex : Lock method deposit(n : Integer) mutex.lock() balance ← balance + n mutex.unlock() method withdraw(n : Integer) deposit(−n)
A megoldás másik része sokkal nehezebb. Az átutalás szekvenciális esetben ilyen lenne:
function transfer(from : Account, to : Account, amount : integer) from.withdraw(amount) to.deposit(amount)
Konkurrens esetben ez a megvalósítás hibás, mivel ha az első utasítás után elveszik a vezérlést az aktuális száltól, és egy másiknak adják, akkor pénz tűnhet el vagy jelenhet meg. Zárolni kell, a megfelelő sorrendben:
function transfer(from : Account, to : Account, amount : integer) if from < to // arbitrary ordering on the locks from.lock() to.lock() else to.lock() from.lock() from.withdraw(amount) to.deposit(amount) from.unlock() to.unlock()
Még bonyolultabb műveletek esetén még több zár szerepelhet, és a műveletnek mindegyikről tudnia kell, tehát nem maradhatnak rejtve.
Nyelvi támogatás
A különböző nyelvek különbözőképpen támogatják a szinkronizációt:
- Az ISO/IEC C szabvány szabványos kölcsönös kizárást biztosít a C11 óta. A jelenlegi ISO/IEC C++ szabvány a C++11 óta biztosít konkurrens programozást Az OpenMP szabvány szerint a kritiokus szakaszok pragmákkal jelölhetők meg; ezt több fordító támogatja. A POSIX pthread API támogatja a zárolást.[6] A Visual C++ a synchronized attribútumot vezeti be, de ez specifikus a COM objektumok, a Windows API és a Visual C++ számára.[7] A C és a C++ könnyen hozzáfér az operációs rendszerek zárolási lehetőségeihez.
- Az Objective-C @synchronized kulcsszavát[8] blokkokra lehet rátenni, tovább az NSLocking protokoll[9] tartalmazza az NSLock,[10] NSRecursiveLock[11] és NSConditionLock osztályokat.[12]
- A C# a lock kulcsszót biztosítja a szálak számára a kizárólagos hozzáférésre.
- A VB.NET SyncLock kulcsszava a C# lockhoz hasonlóan működik.
- A Java a zároláshoz a synchronized kulcsszót használja, legyen az blokk, metódus vagy objektum.[13]
- A Python alacsony szintű mutex mechanizmust biztosít kulcsszó nélkül.[14]
- A Ruby mutex objektumot biztosít, kulcsszót nem.[15]
- Az Ada védett objektumai, amelyek védett alprogramokat, egységeket[16] és randevúkat adnak.[17]
- A PHP fájl alapú zárolásra ad lehetőséget,[18] és Mutex osztályt a pthreads kiterjesztésben.[19]
- Az x86 assemblyben bizonyos műveletek LOCK prefixe azt mutatja, hogy atomoictást garantálnak.
Jegyzetek
- ↑ lock Statement (C# Reference)
- ↑ ThreadPoolPriority, and MethodImplAttribute. MSDN. (Hozzáférés: 2011. november 22.)
- ↑ C# From a Java Developer's Perspective. [2013. január 2-i dátummal az eredetiből archiválva]. (Hozzáférés: 2011. november 22.)
- ↑ Designing Data Tier Components and Passing Data Through Tiers. Microsoft, 2002. augusztus 1. [2008. május 8-i dátummal az eredetiből archiválva]. (Hozzáférés: 2008. május 30.)
- ↑ Peyton Jones, Simon. Beautiful concurrency, Beautiful Code: Leading Programmers Explain How They Think. O'Reilly (2007)
- ↑ Marshall, Dave: Mutual Exclusion Locks, 1999. március 1. (Hozzáférés: 2008. május 30.)
- ↑ Synchronize. msdn.microsoft.com. (Hozzáférés: 2008. május 30.)
- ↑ Apple Threading Reference. Apple, inc. (Hozzáférés: 2009. október 17.)
- ↑ NSLocking Protocol Reference. Apple, inc. (Hozzáférés: 2009. október 17.)
- ↑ NSLock Reference. Apple, inc. (Hozzáférés: 2009. október 17.)
- ↑ NSRecursiveLock Reference. Apple, inc. (Hozzáférés: 2009. október 17.)
- ↑ NSConditionLock Reference. Apple, inc. (Hozzáférés: 2009. október 17.)
- ↑ Synchronization. Sun Microsystems. (Hozzáférés: 2008. május 30.)
- ↑ Lundh, Fredrik: Thread Synchronization Mechanisms in Python, 2007. július 1. [2020. november 1-i dátummal az eredetiből archiválva]. (Hozzáférés: 2008. május 30.)
- ↑ Programming Ruby: Threads and Processes, 2001. (Hozzáférés: 2008. május 30.)
- ↑ ISO/IEC 8652:2007. Protected Units and Protected Objects, Ada 2005 Reference Manual. Hozzáférés ideje: 2010. február 27. „A protected object provides coordinated access to shared data, through calls on its visible protected operations, which can be protected subprograms or protected entries.”
- ↑ ISO/IEC 8652:2007. Example of Tasking and Synchronization, Ada 2005 Reference Manual. Hozzáférés ideje: 2010. február 27.
- ↑ PHP Documentation - flock
- ↑ PHP Documentation - The Mutex class
Fordítás
- Ez a szócikk részben vagy egészben a Lock (computer science) című angol Wikipédia-szócikk fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.