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.

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

szerkesztés

A zárak hardveres támogatást igényelnek ahhoz, hogy hatékonyan lehessen őket használni. Ez rendszerint egy vagy több atomi 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 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álni a MethodImplOptions szinkronizációs attributum használatával.[2][3]

[MethodImpl(MethodImplOptions.Synchronized)]
public void someMethod() {
    // do stuff
}

Szemcsézettség

szerkesztés

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

szerkesztés

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

szerkesztés

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

szerkesztés

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

szerkeszté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.
  1. lock Statement (C# Reference)
  2. ThreadPoolPriority, and MethodImplAttribute. MSDN. (Hozzáférés: 2011. november 22.)
  3. 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.)
  4. 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.)
  5. Peyton Jones, Simon. Beautiful concurrency, Beautiful Code: Leading Programmers Explain How They Think. O'Reilly (2007) 
  6. Marshall, Dave: Mutual Exclusion Locks, 1999. március 1. (Hozzáférés: 2008. május 30.)
  7. Synchronize. msdn.microsoft.com. (Hozzáférés: 2008. május 30.)
  8. Apple Threading Reference. Apple, inc. (Hozzáférés: 2009. október 17.)
  9. NSLocking Protocol Reference. Apple, inc. (Hozzáférés: 2009. október 17.)
  10. NSLock Reference. Apple, inc. (Hozzáférés: 2009. október 17.)
  11. NSRecursiveLock Reference. Apple, inc. (Hozzáférés: 2009. október 17.)
  12. NSConditionLock Reference. Apple, inc. (Hozzáférés: 2009. október 17.)
  13. Synchronization. Sun Microsystems. (Hozzáférés: 2008. május 30.)
  14. 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.)
  15. Programming Ruby: Threads and Processes, 2001. (Hozzáférés: 2008. május 30.)
  16. 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.” 
  17. ISO/IEC 8652:2007. Example of Tasking and Synchronization, Ada 2005 Reference Manual. Hozzáférés ideje: 2010. február 27. 
  18. PHP Documentation - flock
  19. PHP Documentation - The Mutex class. [2017. július 4-i dátummal az eredetiből archiválva]. (Hozzáférés: 2017. június 29.)

Fordítás

szerkeszté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.