Portál AbcLinuxu, 6. května 2025 01:28
Běžící blákno, které může takto rychle získat zámek ...
struct mutex { atomic_t count; spinlock_t wait_lock; struct list_head wait_list; };Jádro pudla je v tomto nižším patře = v kernelovém mutexu. Všimněte si položky "struct list_head wait_list" - to je nějaký spojový seznam "čekatelů na zámek". Uvnitř páru volání mutex_lock()/mutex_unlock() existují dvě cesty: rychlá a pomalá. Pomalá cesta bere spinlock a hraje si s wait_listem (seznam čekajících procesů) = při mutex_lock() se vlákno do seznamu zapíše, při mutex_unlock() se vyškrtne. Naproti tomu rychlá cesta uvnitř mutex_lock() za příznivých okolností jenom "proletí", nepřidává se do wait_listu = nebere spinlock, pouze si atomicky (s podporou CPU) dvakrát sáhne na položku count. Podobně uvnitř mutex_unlock(). Za příznivých okolností, konkrétně když o mutex soupeří dvě vlákna, a to ještě na konci delší "dávky" soupeřících vláken:
Jádro pudla je v tomto nižším patře = v kernelovém mutexu.Strucne receno, mutex_lock() v jednom threadu uspeje drive nez skonci mutex_unlock() v druhem a ten druhy stale pristupuje k interni mutexove strukture. Pokud se na takove chovani podivam z hlediska konvencni semantiky posixovych mutexu (tedy pokud by se tak chovali userspace mutexy), tak mi to jako zavadne neprijde (primarni cil - vzajemne vylouceni kodu *uvnitr* mutexove sekce - je zajisten), akorat mutex_destroy() by musel vzit spinlock a tim se ujistit, ze paralelni mutex_unlock() uz skoncil. To by mohlo byt i adekvatni reseni pro kernelove mutexy.
delete
kdekoliv mimo destruktor smart pointerů pokládám za velmi špatný kód* a race conditions dealokací opět dobře řeší smart pointery používající atomický reference counter. Zde je to zesložitěné tím, že se tam snaží aktivně spouštět jiné vlákno, ale s něčím takovým se v user space setkáte jen výjimečně.
* Schválně za jak dlouho přijdete na to, že tohle leakuje?
class Socket { public: Socket() { if (!connect()) throw std::exception(); } bool connect() { return false; } }; class Test { public: constexpr size_t BUF_SIZE = 4096; char *buf; Socket sock; Test() : buf(new char[BUF_SIZE]()) {} ~Test() { delete[] buf; } }
sock
), tak se nezavolá tělo destruktoru; nakonec to je logické, objekt se ještě nevytvořil, a tak není co ničit. C++ v takovém případě volá destruktory všech již zkonstruovaných členů a předků (pokud by Test
měl předka, tak jeho destruktor se zavolá), zde tedy program zavolá destruktor buf
, jenže to je char *
, který se sám nedealokuje. Pokud by buf
byl smart pointer, tak jeho destruktor tu paměť uklidí.
S výjimkami není problém, pokud dodržujete RAII, tedy že každý zabraný zdroj má vlastní „hlídací“ objekt, který jej v destruktoru uvolní. A tohle pravidlo právě vede k tomu, aby delete
používaly akorát smart pointery (a výrazně zjednodušuje hlídání zdrojů oproti C ~Test() try { ⋮ // Volání funkcí, které mohou vyhodit výjimku, kromě destruktorů — tam by si to měl řešit ničené objekty } catch (...) { if (std::uncaught_exception()) // Případně nějaké logování, pokud vás to zajímá return; // catch blok, kterým končí konstruktor či destruktor, má implicitní rethrow }
noexcept
, je potřeba to zrušit:
~Test noexcept(false)
To je syntaxe ve stylu: „Půjdu na nákup ne.“ To snad museli vymyslet Francouzi.Spíš lidi z Cisca
no shutdown
)
Ale ono zrovna v tomhle případě to je namístě, u drtivé většina destruktoru je noexcept
vhodný.
delete
jsem od té doby, co delete
u kódu, který spravuji, smí používat jen smart pointery, neviděl.
Jádro není v C++ hlavně proto, že v roce 1991 byla podpora C++ dost mizerná. A taky proto, že v jádře bývá problém s implementací výjimek a RTTI, tak se to tam nepoužívá, čímž se C++ snižuje na C with classes. A to už pak rovnou jde psát v C.
Tiskni
Sdílej:
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.