동기화 기법: Lock

김 무무 ㅣ 2024. 10. 5. 16:47

1. Lock이란

멀티스레딩 환경에서 공유 자원에 대한 접근을 제어하는 동기화 메커니즘

Lock의 주요 목적은 여러 스레드가 동시에 같은 자원에 접근하는 것을 방지하여 데이터의 일관성을 유지하는 것이다.

 

주요 Lock 종류:

 

1) 상호 배제 Lock (Mutual Exclusion Lock)

   - 가장 기본적인 형태의 Lock

   - 한 번에 하나의 스레드만 임계 영역에 진입 가능

 

2) 읽기-쓰기 Lock (Read-Write Lock)

   - 읽기 작업과 쓰기 작업을 구분

   - 여러 스레드가 동시에 읽기 가능, 쓰기는 독점적으로 수행

 

3) 재진입 가능 Lock (Reentrant Lock)

   - 같은 스레드가 이미 획득한 Lock을 다시 획득 가능

   - 재귀적 알고리즘이나 중첩된 메서드 호출에서 유용

 

 

2. Lock 구현 방법

 

1) 스핀락 기반 구현

 

Lock을 획득할 때까지 반복해서 시도하는 방식

 

ex)

#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
#include <chrono>
using namespace std;

class SpinLock {
    atomic<bool> flag = { false };
public:
    void Lock() {
        while (flag.exchange(true, memory_order_acquire));
    }
    void Unlock() {
        flag.store(false, memory_order_release);
    }
};

class SharedResource {
private:
    int value;
    SpinLock spinlock;

public:
    SharedResource() : value(0) {}

    void Increment() {
        spinlock.Lock();
        value++;
        // 작업
        this_thread::sleep_for(chrono::milliseconds(1));
        spinlock.Unlock();
    }

    int GetValue() const { return value; }
};

void Worker(SharedResource& resource, int id, int iterations) {
    for (int i = 0; i < iterations; i++) {
        resource.Increment();
        cout << "스레드 " << id << " 실행" << endl;
    }
}

int main() {
    SharedResource resource;
    vector<thread> threads;
    const int THREADS_COUNT = 10;
    const int ITERATIONS_PER_THREAD = 50;

    for (int i = 0; i < THREADS_COUNT; ++i) 
        threads.emplace_back(Worker, ref(resource), i, ITERATIONS_PER_THREAD);

    for (auto& t : threads) 
        t.join();

    cout << "스레드 수 : " << THREADS_COUNT << endl;
    cout << "예상값 : " << (THREADS_COUNT * ITERATIONS_PER_THREAD) << endl;
    cout << "결과값 : " << resource.GetValue() << endl;
}

 

 

2) 대기 큐 기반 구현

 

운영체제의 스케줄러를 활용하여 대기 중인 스레드를 관리하는 방식

 

ex)

class MutexLock {
    atomic<bool> locked = { false };
    queue<thread::id> waitQueue;
public:
    void Lock() {
        while (locked.exchange(true, memory_order_acquire)) {
            waitQueue.push(this_thread::get_id());
            this_thread::yield();
        }
    }
    void Unlock() {
        locked.store(false, memory_order_release);
        if (!waitQueue.empty()) {
            waitQueue.pop();
        }
    }
};

 

 

3. Lock의 장단점

 

장점:

- 간단하고 직관적인 동기화 메커니즘 제공

- 데이터 일관성 보장

 

단점:

1) 데드락 위험

   - 여러 Lock을 사용할 때 발생 가능

   - 예방법: Lock 획득 순서를 일관되게 유지

 

2) 성능 오버헤드

   - Lock 획득/해제에 따른 추가 연산 필요

   - 경쟁 상황에서 대기 시간 발생

 

3) 우선순위 역전 문제

   - 낮은 우선순위 스레드가 Lock을 보유한 채 선점되면, 높은 우선순위 스레드가 대기

'멀티스레딩' 카테고리의 다른 글

데드락(Deadlock)  (1) 2024.10.07
동기화 기법: Mutex  (0) 2024.10.05
동기화 기법: Atomic 연산  (0) 2024.10.05
메모리 구조와 멀티스레딩  (0) 2024.10.03
데이터 동기화 기초  (0) 2024.10.01