Tiny Star

🪄Interview/✏️Study

[CS STUDY INTERVIEW] 8주차 - 락(LOCK)

청크 2024. 4. 12. 23:28

지난 7주차 스터디에 공부한 스레드에서 사용되는 락(LOCK)을 조금 더 자세히 파보는  CS 스터디 8주차 

 

락(LOCK)


락(LOCK)

스레드에서 사용되는 락(LOCK)은 동시성 프로그래밍에서 중요한 개념이다.

여러 스레드가 동시에 돌아가는 멀티 스레드 환경에서 공유 자원을 동시에 접근할 때

발생할 수 있는 충돌을 방지하기 위한 동기화 기술이다.

 

락을 사용함으로써 특정 시점에 자원을 사용할 수 있는 스레드의 갯수를 제한하여 프로그램 동작을 안정적이고 예측가능하도록 보장한다.


락(LOCK)의 기본 개념

상호 배제

이 원칙은 한 번에 하나의 스레드만 공유 자원에 접근할 수 있도록 하여

스레드들이 동시에 같은 데이터를 수정하는 것을 방지한다.

 

상호 배제는 데이터 경합을 방지하거나 데이터의 일관성/정확성을 위해 필요로 한다.

 

데이터 경합(Data race)이란

두 개 이상의 스레드가 동시에 데이터를 수정하려고 할 때 예측되지 않은 결과를 얻을 수 있는 현상인데,

상호 배제를 통해 이를 방지하고 공유 자원의 상태/결과를 항상 예측이 가능하며 일관되도록 보장할 수 있다.

 

상호 배제의 구현

상호 배제를 구현하는 방법에는 다양한 방법이 있다.

 

1. 뮤텍스(Mutexes)

뮤텍스는 "Mutual Exclusion"의 줄임말로, 동시에 하나의 스레드만이 특정 자원 또는 코드 섹션에 대한 

접근 권한을 가질 수 있도록 설계된 도구이다.

 

뮤텍스는 특정 스레드에 의해 소유될 수 있고 소유한 스레드만이 뮤텍스를 해제할 수 있기 때문에

한 스레드가 뮤텍스를 소유하고 있을 때 다른 스레드들은 그 스레드가 뮤텍스를

해제할 때까지 대기를 해야한다.

 

잘못 사용 될 경우 두 스레드가 서로 다른 뮤텍스를 기다리며 상대방이 소유한 뮤텍스를 요구할 때

데드락이 발생될 수 있어 주의가 필요하다.

 

2. 세마포어 (Semaphore)

세마포어는 복수의 스레드가 동시에 리소스의 일정 부분에 접근할 수 있도록 허용하는 일종의 카운팅 메커니즘이다.

 

세마포어는 내부적으로 카운터를 유지하고 이 카운터는 동시에 리소스 접근이 가능한 스레드의 수를 결정한다.

하나 이상의 리소스를 다룰 수 있고 카운터가 0이 아닌동안은 스레드가 리소스에 계속 접근이 가능하다.

 

또한 세마포어는 뮤텍스와 유사하게 동작할 수 있는 이진 세마포어가 가능하다.

이진 세마포어는 카운터가 1인 세마포어로 뮤텍스와 유사하게 작동된다.

 

<뮤텍스와 세마포어>

뮤텍스는 특정 스레드가 소유하고 해제할 수 있으며 주로 상호 배제를 위해 사용되는 반면

세마포어는 소유의 개념 없이 스레드 간 공유가 가능하고 주로 리소스 갯수 제한을 위해 사용된다.

 

세마포어는 뮤텍스보다 더 일반적인 동기화 메커니즘을 제공하여, 다양한 동기화 문제에 적용이 가능하지만

관리가 더 복잡하다는 차이점이 있다.

 

3. 모니터 (Monitors)

모니터는 고수준의 동기화 추상화로, 특정 객체에 대한 접근을 안전하게 제어하는 방법을 제공한다.

모니터 내의 모든 메서드는 자동으로 락을 획득하고 해제하는 기능을 내장하고 있어서

동시에 하나의 스레드만이 객체의 메서드를 실행할 수 있다.

이럼으로써 공유 자원에 대한 동시 접근을 막고 상호 배제를 보장할 수 있는 것이다.

 

대기 중인 스레드를 위해 조건 변수를 제공할 수 있고 스레드는 특정 조건이 충족될 때까지 대기하고,

조건 만족 시 다시 작업을 재개할 수 있는 특징이 있다.

또한 많은 프로그래밍 언어들에서 내장 모니터를 지원하기 때문에 다양한 언어에서 사용이 가능하다.

 

 

4. 크리티컬 섹션 (Critical Section)

프로그램의 코드 내에서 하나의 스레드만 실행할 수 있는 코드 블록을 의미하며,

이 섹션은 공유 자원을 접근하는 코드를 포함한다.

동시에 여러 스레드가 이 부분을 실행하면 데이터 경합이 발생할 수 있기 때문에 크리티컬 섹션을 통해

특정 코드 블록 주변에 상호 배제를 구현하여, 한 시점에 하나의 스레드만이 그 섹션을 실행할 수 있도록 한다.

 

모니터와 다르게 크리티컬 섹션을 사용 할 경우 개발자가 명시적으로 잠금 매커니즘인

뮤텍스 또는 세마포어를 설정하고 해제 해주어야 하며 개발자는 필요에 따라 섹션 정의, 보호 방법을 결정할 수 있다.

 

하지만 잘못 설계된 크리티컬 섹션은 성능 저하가 유발될 수 있으니 잠금/해제는 최소한으로 유지해야한다.


락(LOCK)의 종류

1. 스핀락 (Spinlock)

스핀락은 간단한 형태의 락으로, 스레드가 락을 획득할 때까지 무한 루프(스핀)를 실행하면서 

계속해서 락을 획득하려고 락을 요청하는 방식으로,

CPU를 계속 사용하는 상태이기 때문에 락을 잠깐 사용할 때 효율적이며,

만약 락이 길어진다면 비효율적일 수 있다.

스레드가 락을 획득할 때 까지 CPU에서 계속 실행되기 때문에 컨텍스트 스위치 발생이 없다.

 

2. 블로킹 락(Blocking lock)

락을 획득할 수 없을 때 스레드를 대기 상태로 만들어 CPU 사용량을 절약할 수 있는 특징이 있어서

입출력이 많거나 대기 시간이 길어지는 상황에 효율적이지만

스레드가 대기 상태로 전환되면서 컨텍스트 스위치가 발생하고 이는 스핀락에 비해

성능 오버헤드 발생 가능성이 높다.

 

3. 재진입 가능 락 (Reentrant lock)

동일 스레드가 이미 획득한 락을 다시 요청할 수 있는 락으로 재귀 함수 호출에 사용된다.

 

4. 읽기/쓰기 락(Read/Write lock)

읽기쓰기 작업을 분리하여, 여러 쓰레드가 동시에 읽기 작업을 수행할 수 있도록 하면서

쓰기 작업은 독점적으로 수행할 수 있는 락으로

데이터가 자주 읽히지만 적게 변경되는 경우에 유용한 락이다.


락(LOCK)의 문제점 및 대안

1. 데드락(Deadlock)
여러 스레드가 서로 다른 락을 기다리면서 아무도 진행할 수 없게 되는 상태로,
예를 들어, 스레드 A가 락 1을 가지고 락 2를 요청하고, 스레드 B가 락 2를 가지고 락 1을 요청하는 상황으로 볼 수 있다.

 

2. 라이브락(Livelock)
스레드들이 계속해서 자신의 상태를 변경하면서도 실제로는 진행하지 못하는 상태로,
예를 들어, 두 스레드가 서로 양보하며 락을 얻으려고 계속 시도하지만 결국 어느 쪽도 진행하지 못하는 경우이다.


3. 스타베이션(Starvation)
하나 또는 그 이상의 스레드가 락을 획득하지 못하고 무한히 대기하는 상황으로
우선순위가 낮은 스레드가 높은 우선순위의 스레드에 의해 계속 대기 상태에 놓일 수 있다.

 

대안

1. 락 프리(Lock-free) 프로그래밍
원자적 연산을 사용하여 스레드간의 동기화를 락 없이 구현할 수 있는데, 
대표적인 예로는 원자적 클래스, 비교 및 교환 연산(CAS) 등이 있다.


2. 소프트웨어 트랜잭셔널 메모리(Software Transactional Memory, STM)
데이터 구조에 대한 변경을 트랜잭션으로 처리하여, 실패할 경우 롤백을 실행하기 때문에
락을 사용하지 않고도 여러 스레드가 데이터에 안전하게 접근할 수 있도록 한다.


3. 옵티미스틱 동시성 제어(Optimistic Concurrency Control)
데이터 변경 시 충돌을 감지하기 위해 검증 단계를 사용하여 충돌이 발생하면 작업을 다시 시도한다.


이러한 도구와 전략을 사용하여 락이 가져올 수 있는 문제점을 완화하고, 효율적인 동시성 관리를 구현할 수 있다.


면접 예상 질문과 답변

Q1. 스레드에서 사용되는 락의 목적은 무엇인가?
A1. 스레드에서 사용되는 락은 멀티 스레드 환경에서 여러 스레드가 공유 자원에 동시 접근할 때

발생할 수 있는 데이터 경합을 방지하고자 하는 동기화 기술로 프로그램 동작을 안정적이고 예측가능하게 하기 위한 목적입니다.

 

Q2. 상호 배제(Mutual Exclusion)란 무엇이며 왜 필요한가?
A2.  상호 배제는 한 번에 하나의 스레드만이 공유 자원에 접근할 수 있도록 하는 원칙입니다. 

이는 스레드들이 동시에 같은 데이터를 수정하여 발생할 수 있는 데이터 경합을 방지하고, 데이터의 일관성과 정확성을 유지하기 위해 필요합니다.

 

Q3. 뮤텍스와 세마포어의 차이점?
A3. 뮤텍스는 상호 배제를 위해 사용되며, 한 번에 하나의 스레드만이 자원에 접근할 수 있도록 합니다.

반면, 세마포어는 카운터를 기반으로 하여 여러 스레드가 동시에 자원에 접근할 수 있는 허용 개수를 관리합니다.

세마포어는 뮤텍스보다 더 일반적인 동기화 메커니즘을 제공하지만 관리가 더 복잡합니다.

 

Q4. 데드락이란 무엇이며 어떻게 발생하나?
A4. 데드락은 여러 스레드가 서로 다른 락을 기다리면서 아무도 진행할 수 없게 되는 상태를 말합니다.

예를 들어, 스레드 A가 락 1을 가지고 락 2를 요청하고, 스레드 B가 락 2를 가지고 락 1을 요청하는 경우 데드락이 발생할 수 있습니다.

 

Q5. 락 프리 프로그래밍의 장점은 무엇인가?
A5. 락 프리 프로그래밍은 원자적 연산을 사용하여 락을 사용하지 않고도 스레드 간의 동기화를 달성합니다.

이 접근 방식은 데드락이나 라이브락 같은 문제를 피할 수 있으며, 시스템의 반응성과 처리량을 향상시킬 수 있습니다.


Q6. 모니터와 크리티컬 섹션은 어떻게 다른가?
A6. 모니터는 고수준의 동기화 메커니즘으로, 특정 객체에 대한 모든 접근을 자동으로 락을 통해 동기화합니다.

이는 객체의 메서드 호출 시 자동으로 락을 획득하고 해제합니다.

반면, 크리티컬 섹션은 코드의 특정 부분을 보호하기 위해 개발자가 명시적으로 락을 설정하고 해제해야 하는 저수준 동기화 방식입니다.

크리티컬 섹션은 개발자가 직접 잠금 매커니즘을 관리해야 하며, 특정 코드 블록 주변에 상호 배제를 구현합니다.


Q7. 스핀락과 블로킹 락의 차이점은 무엇인가?
A7. 스핀락은 스레드가 락을 획득할 때까지 CPU를 계속 사용하면서 락을 획득하려고 시도하는 락입니다.

이는 락을 잠깐 동안만 사용할 때 효율적입니다. 반면, 블로킹 락은 스레드가 락을 획득할 수 없을 때

대기 상태로 전환되어 CPU 자원을 절약할 수 있습니다. 이 방식은 락을 오래 동안 유지해야 할 때 더 적합합니다.


Q8. 재진입 가능 락(Reentrant Lock)이 필요한 이유?
A8. 재진입 가능 락은 동일 스레드가 락을 이미 획득한 상태에서 같은 락을 다시 요청할 수 있게 합니다.

이는 재귀적 함수 호출이나 한 스레드 내에서 여러 번 락을 요구하는 코드를 안전하게 실행할 수 있도록 도와줍니다.

일반 락에서는 스레드가 자신이 이미 획득한 락을 다시 요청하면 데드락에 빠질 수 있습니다.


Q9. 읽기/쓰기 락의 사용 사례를 설명해라.
A9. 읽기/쓰기 락은 데이터가 자주 읽히지만 상대적으로 적게 변경될 때 유용합니다.

이 락을 사용하면 여러 스레드가 동시에 데이터를 읽을 수 있도록 허용하면서, 쓰기 작업을 할 때는 독점적으로 접근을 제한합니다.

예를 들어, 온라인 도서관의 도서 검색 시스템에서는 많은 사용자가 동시에 도서 정보를 조회할 수 있지만,

도서 정보를 수정하는 작업은 한 번에 하나의 스레드만 수행할 수 있습니다.

 

Q10. 라이브락과 데드락의 차이는 무엇인가?
A10. 데드락은 두 개 이상의 스레드가 서로의 락을 기다리면서, 아무도 진행할 수 없는 상태에 빠지는 것을 말합니다.

반면, 라이브락은 스레드들이 계속해서 상태를 변경하면서도 실제로는 진행하지 못하는 상태입니다.

라이브락은 스레드들이 서로를 기다리지 않고 반복적으로 자신의 상태를 변경하지만, 결국에는 아무런 진전이 없는 상황을 말합니다.