임계영역
여러 프로세스 또는 스레드가 공유하는 자원에 접근하는 코드 부분을 의미
즉, 동시에 둘 이상의 프로세스/스레드가 실행되면 문제가 발생할 수 있는 코드 영역을 의미
공유자원
멀티스레딩 또는 멀티프로세싱 환경에서 둘 이상의 스레드나 프로세스가
동시에 접근하거나 사용할 수 있는 시스템 자원이나 데이터를 의미
Ex) 전역변수, 데이터베이스 연결 객체, 파일, 네트워크 소켓, 프린터, 메모리 버퍼, 카운터 변수
큐 또는 스택, 로깅 시스템 등등
락
락은 하나의 변수이다.
락을 사용하기 위해서는 락 변수를 선언해야 한다.
** 락 변수는 락의 상태를 나타냄
두 가지 상태
- 사용 가능 상태 ( unlocked, free )
- 사용중 (acquired) 즉, 임계 영역에서 정확히 하나의 쓰레드가 락을 획득한 상태
락 자료 구조에 락을 보유한 쓰레드에 대한 정보나 락을 대기하는 쓰레드들에 대한 정보 저장 가능
이러한 정보는 락 사용자는 모름
lock() 함수
락 획득을 시도하는 함수
만약 어떤 쓰레드도 락을 갖고 있지 않다면, 그 쓰레드는 락을 획득하여 임계 영역 내로 진입한다.
** 이렇게 진입한 스레드를 락 소유자(Owner)라고 부른다.
만약 다른 스레드가 lock()를 호출한다면 사용 중인 동안에는 lock() 함수가 리턴하지 않는다.
unlock() 함수
락이 다시 사용 가능한 상태가 됨
락을 대기하는 쓰레드가 없다면 락의 상태는 사용 가능 상태로 유지 됨
만약 대기 중이던 쓰레드가 있다면 락의 상태가 변경되었다는 것을 인지하고 락을 획득하여 임계영역 내로 진입
일반적으로 쓰레드는 프로그래머가 생성하고 운영체제가 제어한다.
락을 사용하여 쓰레드에 대한 제어권을 일부를 받을 수 있다.
락으로 코드를 감싸서 프로그래머는 그 코드 내에서는 하나의 쓰레드만 동작하도록 보장할 수 있다.
즉, 임계 영역에 락을 사용하여 한 번에 한 스레드만 접근하도록 구현할 수 있다.
락을 통해 프로세스들의 혼란스런 실행 순서에 어느 정도를 질서를 부여할 수 있다.
Pthread 락
쓰레드 간 상호배제 기능을 제공하기 때문에 POSIX 라이브러리는 락을 Mutex라고 부른다.
lock() 호출과 unlock() 호출 사이의 코드를 감싸는 것 즉, 두 호출 사이의 코드가 임계영역이 된다.
상호배제 (Mutex)
한 쓰레드가 임계영역 내에 있다면 이 쓰레드의 동작이 끝날 때까지
다른 쓰레드에 들어 올 수 없도록 제한하는 것을 의미
락 전략
1. 거친 전략
- 하나의 락이 임의의 임계 영역에 진입할 때 사용
- 하나의 큰 임계영역
2. 세밀한 전략
- 여러 락을 사용하여 한 번에 여러 쓰레드가 서로 다른 락으로 보호된 코드 내에 각자가 진입 가능
- 여러 개의 작은 임계영역
락 동작 평가 기준
1. 상호배제를 지원하는가
- 락이 동작하여 임계 영역 내로 다수의 쓰레드의 진입을 막을 수 있는지 검사
2. 공정성
- 락 획득에 대한 공정한 기회가 주어지는가 ?? // 기아 상태가 발생하지 않는가 ?
3. 성능
- 락 사용 시간적 오버헤드 평가
인터럽트 비활성화
** 단일 프로세스 환경에서 인터럽트를 막으면, 임계영역 내의 코드에서는
인터럽트가 발생할 수 없기 때문에 원자적으로 실행될 수 있다.
// pintOS는 단일 프로세스 환경
→ 인터럽트 비활성을 통한 동기화 // 원자성 보장, 간단한 구현, 경쟁 상태 방지, 데드락 방지
즉, 다른 쓰레드가 중간에 끼어들지 못해 동기화 효과가 발생하여 원자적 실행, 경쟁상태와 데드락 방지 효과가 발생
- 원자적 실행 : 인터럽트가 비활성화되면, 문맥교환이 발생하지 않아
현재 실행 중인 코드는 중단 없이 실행됨
- 경쟁 상태 방지 : 다른 쓰레드나 인터럽트 핸들러가 공유자원에 접근 불가하므로,
동시 접근으로 인한 경쟁 상태 방지
- 데드락 방지 : 인터럽트가 비활성되었기에 컨텍스트 스위칭이 일어나지 않는다.
따라서 여러 쓰레드가 서로의 자원을 기다리며 교착 상태에 빠질 가능성이 줄어든다.
- 동기화 효과 : 인터럽트 비활성화는 전체 시스템에 대한 “거대한 락”을 거는 것과 유사한 효과 발생
### 단점
1. 응용 프로그램을 너무 많이 신뢰해야 함
→ 탐욕적 기법을 사용한 프로그램이 시작과 동시에 락을 걸면 프로세서를 독점적으로 사용
2. 멀티프로세서에서는 적용 불가
→ 여러 쓰레드가 여러 CPU에서 실행 중이면 각 쓰레드가 동일한 임계영역에 진입 시도할 수 있음
→ 특정 프로세서에서의 인터럽트 비활성화는 다른 프로세서에서 실행 중인 프로그램에는 전혀 영향을 주지 않는다.
3. 장시간 인터럽트 중지로 인해, 중요한 인터럽트 시점을 놓칠 수 있다.
Ex) CPU가 저장 장치에서 읽기 요청을 마친 사실을 모르고 지나가게 되고 OS는 이 읽기 결과를 기다리는 프로세스를 깨울 수 없게 된다.
4. 비효율적인 방법이다.
→ 인터럽트 비활성화 코드들이 최신 CPU들에서는 느리게 실행되는 경향이 있음
**
그러므로 상호배제를 위한 인터럽트 비활성화는 제한된 범위에서만 사용되어야 한다.
- 운영체제가 내부 자료 구조에 대한 원자적 연산을 실행하는 경우
- 복잡해 보이는 인터럽트 발생을 방지하는 경우