Self-deadlock (önmagával szembeni holtpont) akkor alakulhat ki, amikor ugyanaz a szál kétszer akarja megszerezni ugyanazt a (nem rekurzív) mutexet.
Egyszerű self-deadlock példa
| |
f() meghívásakor lock1-en keresztül megszerzésre kerül a mutex (mtx). Ezt követően lock2-n keresztül ugyanezt a mutexet kívánjuk ismét megszerezni, de mivel már a mutexet már ugyanaz a szál birtokolja, a második lockolási kísérlet blokkolódik. Elkezdődik egy várakozás mtx feoldására, viszont az csak akkor történhet meg, amikor lock1 kikerül a hatókörből, azaz f() futása végén.
Persze ilyen jellegű hibát ritkán követünk el, ezért ez teljesen életszerűtlennek tűnik. A probléma különösen veszélyes, ha a lockolás közvetve, több függvényen keresztül történik, mert az egyes függvények önmagukban helyesnek tűnhetnek.
Rejtett self-deadlock
Nézzünk egy példát rejtett(ebb) self-deadlock-ra.
| |
Két metódusunk van, melyek ugyanazt a mutexet lockolják. Mivel Increment meghívja a Print()-et, a Print()-beli lockolási kísérletnél deadlock alakul ki.
Self-deadlock elkerülése std::recursive_mutex használatával
C++-ban létezik a rekurzív mutex (std::recursive_mutex), mely megengedi, hogy ugyanaz a szál többször is megszerezze ugyanazt a mutexet. Emiatt a fenti példák rekurzív mutex használata esetén nem vezethetnek self-deadlock-hoz.
| |
Rekurzív mutex használata esetén is figyelni kell arra, hogy a mutexet ugyanannyiszor kell lockolni, mint ahányszor feloldjuk (
unlock). Szerencsérestd::lock_guardhasználatával garantált, hogy a lock/unlock párok száma rendben lesz, mert a lezárás a konstruktorban valósul meg, a feloldás pedig a destruktorban.
Az
std::recursive_mutexhasználata azonban nem feltétlenül az ideális megoldás a felmerülő tervezési problémára. Gyakran célszerűbb a mutexelés struktúráját átalakítani.
Helper függvények használatakor kialakuló deadlock
Tekintsünk egy olyan példát, amikor egy folyószámla egyenlegét szeretnénk mutex-szel védeni. Van egy BankAccount osztályunk, amelynek mind publikus, mint privát (helper) metódusai mutex-szel védik a balance adattagot.
| |
Ez egy tipikus példája a self-deadlock-nak, hiszen minden függvény védi az adattagot, de ezzel előidézik holtpont kialakulását.
Publikus lockol, privát nem lockol
A probléma egyik lehetséges feloldása lehet, ha a publikus metódusok mutex-szel védik az adattagot, míg a privát (helper) metódusok már nem nyújtanak ilyen védelmet, hanem “bíznak” a publikus metódusok által nyújott védelemben.
| |
Felhívom a figyelmet arra, hogy a példában
PrintBalanceImpl()önmagában nem szálbiztos: meghívója felelőssége, hogy a mutex már lockolva legyen.
Ezt a mintát lockek/unlocked member function pattern-nek is szokás nevezni.