Está familiarizado com mecanismos de sincronização, como spinlocks e bloqueios mútuos (deadlock)?
sobes.tech IA
Resposta da IA
Sim, conheço.
Spinlock é um mecanismo de sincronização em que um thread que não consegue aceder a um recurso verifica continuamente em ciclo, "girando" (spinning), em vez de passar para um estado de espera (bloqueio). É eficiente para proteger secções críticas que se espera que sejam ocupadas por pouco tempo. Evita os custos de mudança de contexto, mas pode consumir muito tempo de CPU se o bloqueio for mantido durante muito tempo.
Mutex (do inglês "mutually exclusive" - mutuamente exclusivo) é um objeto de sincronização que permite que vários threads partilhem um recurso, mas garante que, num dado momento, apenas um thread utilize o recurso. Se um thread não consegue obter o mutex, fica bloqueado e passa para um estado de espera, libertando a CPU. Os mutex são adequados para proteger secções críticas que podem estar ocupadas por mais tempo.
O deadlock (impasse) é uma situação em que dois ou mais threads esperam indefinidamente que o outro libere recursos. Ocorre quando todas as quatro condições de Coffman são satisfeitas:
- Exclusão mútua: Os recursos não podem ser utilizados por vários threads ao mesmo tempo.
- Retenção e espera: Os threads mantêm os recursos que já possuem e esperam por outros.
- Ausência de libertação forçada: Os recursos não podem ser retirados à força dos threads que os detêm.
- Espera cíclica: Existe um ciclo de dois ou mais threads, cada um esperando por um recurso detido pelo próximo no ciclo.
Para evitar deadlocks, podem-se usar estratégias como:
- Evitar obter múltiplos bloqueios ao mesmo tempo.
- Definir uma ordem para obter os bloqueios.
- Utilizar mecanismos de deteção e resolução de deadlocks.
Exemplo de spinlock em C++ usando 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();
// Secção crítica
std::cout << "Thread " << std::this_thread::get_id() << " entrou na secção crítica\n";
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // Simulação de trabalho
std::cout << "Thread " << std::this_thread::get_id() << " sai da secção crítica\n";
spin.unlock();
}
// int main() {
// std::thread t1(worker);
// std::thread t2(worker);
// t1.join();
// t2.join();
// return 0;
// }
Exemplo de mutex em C++ usando std::mutex:
#include <mutex>
#include <thread>
#include <iostream>
std::mutex mtx;
void worker_mutex() {
mtx.lock();
// Secção crítica
std::cout << "Thread " << std::this_thread::get_id() << " entrou na secção crítica\n";
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // Simulação de trabalho
std::cout << "Thread " << std::this_thread::get_id() << " sai da secção crítica\n";
mtx.unlock();
}
// int main() {
// std::thread t1(worker_mutex);
// std::thread t2(worker_mutex);
// t1.join();
// t2.join();
// return 0;
// }
Exemplo de possível deadlock:
#include <mutex>
#include <thread>
#include <iostream>
std::mutex mutex1;
std::mutex mutex2;
// Função que primeiro bloqueia mutex1, depois mutex2
void thread1_func() {
mutex1.lock();
std::cout << "Thread 1 bloqueou mutex1\n";
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Simulação de trabalho
mutex2.lock(); // Thread 1 espera mutex2, que pode estar ocupado pelo thread 2
std::cout << "Thread 1 bloqueou mutex2\n";
// Secção crítica com ambos os mutexes
mutex2.unlock();
mutex1.unlock();
}
// Função que primeiro bloqueia mutex2, depois mutex1
void thread2_func() {
mutex2.lock();
std::cout << "Thread 2 bloqueou mutex2\n";
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Simulação de trabalho
mutex1.lock(); // Thread 2 espera mutex1, que pode estar ocupado pelo thread 1
std::cout << "Thread 2 bloqueou mutex1\n";
// Secção crítica com ambos os mutexes
mutex1.unlock();
mutex2.unlock();
}
// int main() {
// std::thread t1(thread1_func);
// std::thread t2(thread2_func);
// t1.join();
// t2.join();
// return 0;
// }
Neste exemplo, se o thread 1 bloquear mutex1 e depois dormir, e o thread 2 bloquear mutex2 e também dormir, então o thread 1 aguardará mutex2 (que está ocupado pelo thread 2), e o thread 2 aguardará mutex1 (que está ocupado pelo thread 1). Isto resulta num deadlock.
Para evitar isto, pode-se simplesmente alterar a ordem de aquisição dos mutexes numa das funções, para que seja a mesma em ambas, por exemplo, primeiro mutex1, depois mutex2.