SW 사관학교 정글(Jungle)/운영체제-PintOS

[Pintos : 동기화] 락(Lock)

jinsang-2 2024. 9. 25. 16:02

락(Lock)

  • 세마포어와 유사하지만 주로 리소스에 대한 상호 배제를 보장하기 위해 사용된다.
  • 세마포어처럼 동작하지만 초기값이 1인 세마포어와 동일한 개념이다. 
락의 목적

한 번에 하나의 스레드만 특정 리소스에 접근할 수 있도록 보장

락의 연산

 

  • 락 획득(Acquire): 세마포어의 down 연산에 해당하며, 락을 잠그고 현재 스레드가 리소스를 사용하게 만듭니다.
  • 락 해제(Release): 세마포어의 up 연산에 해당하며, 락을 풀고 다른 대기 중인 스레드가 리소스를 사용할 수 있도록 합니다.

락의 추가적인 제약

세마포어와 비교했을 때, 락에는 하나의 중요한 제약이 있습니다. 락을 획득한 스레드, 즉 `락의 소유자(owner)`만 `그 락을 해제`할 수 있습니다. 만약 다른 스레드가 락을 해제하려고 시도하면, 이는 잘못된 사용입니다. 반대로, 이 제약이 문제가 되는 상황이라면 락 대신 세마포어를 사용하는 것이 더 적합합니다.

또한, Pintos에서 락은 **재귀적(recursive)**이지 않습니다. 즉, 이미 락을 소유하고 있는 스레드가 같은 락을 다시 획득하려고 시도하면 오류가 발생합니다. 이 점은 중요하며, 락을 잘못 사용하는 상황을 방지해 줍니다.

 

주요 함수 설명

  • struct lock: 락을 나타내는 구조체입니다. 이 구조체는 락의 상태 및 소유 스레드를 추적합니다.
  • void lock_init(struct lock *lock): 새로운 락을 초기화합니다. 초기 상태에서는 어느 스레드도 락을 소유하지 않으며 사용 준비가 된 상태입니다.
  • void lock_acquire(struct lock *lock): 현재 스레드가 락을 획득하려고 시도합니다. 만약 다른 스레드가 이미 락을 소유하고 있다면, 그 스레드가 락을 해제할 때까지 대기하게 됩니다. 락이 해제되면 현재 스레드가 락을 소유하게 됩니다.
  • bool lock_try_acquire(struct lock *lock): 현재 스레드가 락을 대기하지 않고 즉시 획득하려고 시도합니다. 락을 성공적으로 획득하면 true를 반환하고, 이미 다른 스레드가 락을 소유하고 있으면 false를 반환합니다. 이 함수는 CPU 시간을 낭비하지 않도록 해야 하므로, 락을 반복적으로 시도하지 않고 lock_acquire()와 같은 방법을 사용하는 것이 더 적절합니다.
  • void lock_release(struct lock *lock): 현재 스레드가 락을 해제합니다. 락을 소유한 스레드만 이 함수를 호출할 수 있으며, 다른 스레드가 해제하려고 시도하는 것은 오류입니다.
  • bool lock_held_by_current_thread(const struct lock *lock): 현재 스레드가 락을 소유하고 있는지를 확인하는 함수입니다. 현재 스레드가 락을 소유하고 있으면 true를 반환하고, 그렇지 않으면 false를 반환합니다. 하지만 임의의 스레드가 락을 소유하고 있는지를 확인하는 함수는 제공되지 않습니다. 왜냐하면 다른 스레드가 락을 소유하고 있더라도, 그 정보가 반환된 후 바로 락이 풀릴 수 있기 때문에 의미가 없기 때문입니다.

락의 사용 예제

struct lock my_lock;

void threadA (void) {
    lock_acquire(&my_lock);  // 스레드 A가 락을 획득
    // 중요한 리소스를 사용하는 코드
    lock_release(&my_lock);  // 스레드 A가 락을 해제
}

void threadB (void) {
    lock_acquire(&my_lock);  // 스레드 B도 락을 획득하려 하지만, 스레드 A가 해제할 때까지 기다림
    // 중요한 리소스를 사용하는 코드
    lock_release(&my_lock);  // 스레드 B가 락을 해제
}

void main (void) {
    lock_init(&my_lock);  // 락 초기화
    thread_create("threadA", PRI_MIN, threadA, NULL);  // 스레드 A 생성
    thread_create("threadB", PRI_MIN, threadB, NULL);  // 스레드 B 생성
}

 

 

위의 예제에서, 스레드 A와 스레드 B는 같은 락을 사용하여 중요한 리소스에 접근합니다. 만약 스레드 A가 리소스를 사용 중이라면, 스레드 B는 락이 해제될 때까지 기다렸다가 리소스를 사용할 수 있습니다. 락을 사용하면 두 스레드가 동시에 같은 리소스에 접근하여 발생할 수 있는 **경쟁 상태(race condition)**를 방지할 수 있습니다.

요약

  • 은 세마포어와 비슷하지만, 락의 소유자만 해제할 수 있는 제약이 있습니다.
  • 락은 재귀적이지 않으므로, 락을 이미 소유한 스레드가 다시 획득하려고 시도하면 오류가 발생합니다.
  • 락은 **상호 배제(Mutual Exclusion)**를 보장하기 위해 사용되며, 리소스가 한 번에 하나의 스레드에 의해서만 사용되도록 만듭니다.
  • Pintos에서 락은 주로 커널 수준에서 동기화를 위해 사용되며, 올바르게 사용하지 않으면 시스템의 안정성에 영향을 미칠 수 있습니다.