Sobes.tech
Middle+

Poznáte mechanizmy synchronizácie, ako sú spinlocky a vzájomné blokácie (deadlock)?

sobes.tech AI

Odpoveď od AI

Áno, poznám.

Spinlock je mechanizmus synchronizácie, pri ktorom vlákno, ktoré nedokáže získať prístup ku zdroju, neustále kontroluje jeho dostupnosť v cykle, "točí" sa (spinning), namiesto toho, aby prešlo do stavu čakania (blokovania). Je efektívny na ochranu kritických sekcií, ktoré sa očakáva, že budú zaberané veľmi krátky čas. Vyhýba sa nákladom na prepínanie kontextu, ale môže spotrebovať veľa procesorového času, ak je zámok držaný dlhý čas.

Mutex (z anglického mutually exclusive - vzájomne vylučujúci) je objekt synchronizácie, ktorý umožňuje viacerým vláknam zdieľať jeden zdroj, ale zabezpečuje, že v danom momente je zdroj používaný iba jedným vláknom. Ak vlákno nemôže získať mutex, zablokuje sa a prejde do stavu čakania, uvoľňujúc procesor. Mutexy sú vhodné na ochranu kritických sekcií, ktoré môžu byť zaberané dlhšie.

Vzájové blokovanie (deadlock) je situácia, keď dve alebo viac vlákien navždy čakajú na seba, aby uvoľnili zdroje. Vzniká, keď sú splnené všetky štyri podmienky Coffman:

  1. Vzájomné vylučovanie: Zdroje nemôžu byť použité súčasne viacerými vláknami.
  2. Udržiavanie a čakanie: Vlákna držia už pridelené zdroje a čakajú na nové.
  3. Chýbajúce nútené uvoľnenie: Zdroje nemôžu byť nútene odobrané od vlákien, ktoré ich držia.
  4. Cyklické čakanie: Existuje cyklus z dvoch alebo viacerých vlákien, z ktorých každé čaká na zdroj, ktorý je obsadený ďalším vláknom v cykle.

Na prevenciu vzájomného blokovania je možné použiť rôzne stratégie, napríklad:

  • Vyhýbať sa získavaniu viacerých zámkov naraz.
  • Určiť poradie získavania zámkov.
  • Používať mechanizmy na detekciu a odstránenie vzájomného blokovania.

Príklad spinlocku v C++ s použitím std::atomic_flag:

#include <atomic>
#include <thread>
#include <iostream>

class Spinlock {
    std::atomic_flag flag = ATOMIC_FLAG_INIT;

public:
    void lock() {
        while (flag.test_and_set(std::memory_order_acquire));
    }

    void unlock() {
        flag.clear(std::memory_order_release);
    }
};

Spinlock spin;
void worker() {
    spin.lock();
    // Kritická sekcia
    std::cout << "Vlákno " << std::this_thread::get_id() << " vstúpilo do kritickej sekcie\n";
    std::this_thread::sleep_for(std::chrono::milliseconds(10)); // Napodobnenie práce
    std::cout << "Vlákno " << std::this_thread::get_id() << " opúšťa kritickú sekciu\n";
    spin.unlock();
}
// int main() {
//     std::thread t1(worker);
//     std::thread t2(worker);
//     t1.join();
//     t2.join();
//     return 0;
// }

Príklad mutexu v C++ s použitím std::mutex:

#include <mutex>
#include <thread>
#include <iostream>

std::mutex mtx;
void worker_mutex() {
    mtx.lock();
    // Kritická sekcia
    std::cout << "Vlákno " << std::this_thread::get_id() << " vstúpilo do kritickej sekcie\n";
    std::this_thread::sleep_for(std::chrono::milliseconds(10)); // Napodobnenie práce
    std::cout << "Vlákno " << std::this_thread::get_id() << " opúšťa kritickú sekciu\n";
    mtx.unlock();
}
// int main() {
//     std::thread t1(worker_mutex);
//     std::thread t2(worker_mutex);
//     t1.join();
//     t2.join();
//     return 0;
// }

Príklad možného vzájomného blokovania:

#include <mutex>
#include <thread>
#include <iostream>

std::mutex mutex1;
std::mutex mutex2;

// Funkcia, ktorá najskôr získa mutex1, potom mutex2
void thread1_func() {
    mutex1.lock();
    std::cout << "Vlákno 1 získalo mutex1\n";
    std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Napodobnenie práce
    mutex2.lock(); // Vlákno 1 čaká na mutex2, ktorý môže byť obsadený vláknom 2
    std::cout << "Vlákno 1 získalo mutex2\n";
    // Kritická sekcia s oboma mutexmi
    mutex2.unlock();
    mutex1.unlock();
}
// Funkcia, ktorá najskôr získa mutex2, potom mutex1
void thread2_func() {
    mutex2.lock();
    std::cout << "Vlákno 2 získalo mutex2\n";
    std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Napodobnenie práce
    mutex1.lock(); // Vlákno 2 čaká na mutex1, ktorý môže byť obsadený vláknom 1
    std::cout << "Vlákno 2 získalo mutex1\n";
    // Kritická sekcia s oboma mutexmi
    mutex1.unlock();
    mutex2.unlock();
}
// int main() {
//     std::thread t1(thread1_func);
//     std::thread t2(thread2_func);
//     t1.join();
//     t2.join();
//     return 0;
// }

V tomto príklade, ak vlákno 1 získa mutex1 a potom zaspí, a vlákno 2 získa mutex2 a zaspí, potom neskôr vlákno 1 bude čakať na mutex2 (ktorý je obsadený vlákno 2), a vlákno 2 bude čakať na mutex1 (ktorý je obsadený vlákno 1). Dochádza k vzájomnému zablokovaniu.

Na jeho odstránenie by ste mohli jednoducho zmeniť poradie získavania mutexov v jednej z funkcií, aby obe funkcie najskôr získavali mutex1, a potom mutex2.