프로세서는 한 번에 한 개의 프로세스만 실행시킬 수 있다. 하지만 운영체제가 프로세서를 빠르게 전환하여 프로세서 시간을 나눠서 마치 여러 프로세스가 동시에 실행되는 것처럼 보이는 것을 병행 프로세스라고 한다.
병행과 병렬
병행과 병렬 둘 다 대략적으로 '동일한 시간 동안 여러 프로세스를 동시 실행'이라는 의미를 가진다.
하지만 둘은 전혀 다르다. 병렬을 이용한 병렬 컴퓨팅은 다중 프로세서 시스템에서 동일한 시간대에 별도의 프로세서에서 실행하는 것이다. 반면에 병행을 이용한 동시 컴퓨팅은 프로세스 수명이 겹치는 것이지 꼭 동일한 시간대에 실행될 필요가 없다.
병행 프로세스는 단일 처리 시스템에서 서로 독립적으로 작업을 수행하는 독립 프로세스와 다른 프로세스와 협력하면서 특정 기능을 수행하는 비동기적 병행 프로세스인 협력 프로세스로 구분한다.
상호배제는 병행 프로세스에서 프로세스 하나가 공유 자원을 사용할 때 다른 프로세스들이 동일한 일을 할 수 없도록 하는 방법이다.

당연하게도 읽기 연산 경우 동시에 접근해도 문제가 발생하지 않지만, 변수나 파일은 프로세스 차례대로 읽거나 쓰도록 해야하는데 공유 자원을 동시에 사용하지 못하게 실행을 제어하는 방법을 동기화라고 한다. 동기화를 통해 상호배제를 보장할 수 있지만 교착 상태 또는 기아 상태가 발생할 수 있다.
다수의 프로세스가 접근할 수 있지만, 어느 한순간에는 프로세스 하나만 사용할 수 있는 영역을 의미한다.
즉, 어떤 프로세스가 임계 영역에 들어가면 다른 프로세스는 임계 영역에 접근할 수 없다 .
생산자와 소비자는 둘 다 동시에 공유 자원(Counter)에 접근할 수 있어 임계 영역의 문제가 발생할 수 있는데 이를 상호배제로 해결할 수 있다.
상호배제 방법으로는 보통 고급 수준과 저급 수준으로 구분된다.
세마포어는 값이 음이 아닌 정수인 플래그 변수로 기차의 진행 가능 여부를 알려주는 차단기가 대표적인 예시다.
운영체제는 세마포를 이용해 여러 개의 프로세스가 동시에 접근할 수 있는 자원으로 공유 자원에 접근할 수 있는 수량을 제어하는데 이는 P연산과 V연산으로 수행된다.
wait()): 자원 사용 연산으로 세마포어 값이 1 이상일 경우 세마포어의 값을 1 감소시키며 임계 영역으로 진입한다. 만약 세마포어 값 0이라면 해당 자원이 사용중이므로 임계 영역에 들어갈 수 없어 프로세스는 Block 상태로 전환되며 대기 큐에 들어간다.signal()): 자원 반환 연산으로 세마포어 값 1 증가시키며 임계 영역에서 탈출한다. 만약 대기 큐에 Block 상태의 프로세스가 존재한다면 적절하게 하나를 선택해 임계 영역으로 진입시킨다.세마포어는 설계에 따라 계수(카운팅) 세마포어와 이진 세마포어로 구분할 수 있다.
count를 자원사용의 허가 값으로 사용한다.S(count)--;가 일어난다. 만약, count == 0이라면 사용 불가능 상태를 의미하여 자원을 사용할 수 없다.S(count)++;를 수행한다.이진 세마포어와 계수 세마포어 둘 다 모든 프로세스가 공유할 수 있는 전역 자원이다.
세마포어에서는 wait()연산 때문에 대기하는 프로세스들이 교착 상태에 빠질 수 있다.
예를들어 프로세스는 한 번에 세마포어 하나만 대기할 수 있어 자원을 할당하는 상황에서 두 프로세스가 각각 자원을 하나씩 보유하고, 상대방의 자원을 사용하려고 대기하는 상황이 발생할 수 있다.
세마포어는 상호배제와 프로세스 사이를 조정하고 유연성 있는 강력한 도구지만 모든 프로세스가 1로 초기화한 세마포를 공유하고, 임계 영역에 진입하기 전에 signal() 연산 후 wait() 연산을 실행해야 한다.
만약, 이 순서를 지키지 않는다면 상호배제를 위반하거나 교착 상태가 발생하는데, 자칫 여러 가지 오류가 쉽게 발생할 수 있다. 그래서 이런 단점을 극복하고자 모니터가 등장했다.
모니터는 공유 자원과 이것의 임계 영역을 관리하는 소프트웨어 집합체로, 해당 타입의 상태를 정의하는 변수들과 그 변수를 조작하는 함수들로 구성된다.

모니터 타입으로 표현된 구조체는 여러 프로세스가 직접 접근할 수 없고, 모니터 내부에 정의된 함수들만 이 변수와 파라미터에 접근할 수 있다. 즉, 모니터의 지역 변수는 모니터 내부 함수로 모니터 안으로 진입해 공유 데이터에 접근할 수 있다는 것이다. 무엇보다 언제나 한 번에 한 개의 프로세스만 모니터에 진입할 수 있도록 제한하여 상호배제를 실현하는 것이 중요하다.
모니터안으로 진입하지 못하도록 차단된 프로세스들은 준비 큐에서 진압을 기다리게 해 상호배제를 실현한다.
이처럼 모니터는 한 번에 하나의 프로세스만 내부에서 활동할 수 있도록 보장하기 때문에, 프로그래머가 따로 동기화 제약 조건을 코딩할 필요가 없다.
기존의 모니터만으로는 모든 동기화 상황을 표현하기엔 부족하다. 그래서 조건 변수라는 추가적인 동기화 메커니즘을 사용한다. 조건 변수은 wait()과 signal() 두 연산으로 변경된다.
x.signal();을 호출해줘야 한다.세마포어 signal()과 모니터 signal()
- 세마포어의
signal()은 세마포어 값을 1을 증가시켜 항상 상태 변화가 발생하지만, 모니터signal()은 아무도 대기하지 않으면 아무일도 일어나지 않아 항상 상태 변화가 일어나지 않는 차이가 있다.
임계 영역 문제에 대한 고급 수준의 소프트웨어 도구 중 간단한 도구로 임계 영역을 보호하기 위해 사용되면서 경쟁 조건을 방지한다. 프로세스가 임계 영역으로 들어가기 전 락을 획득해야 하고 임계 영역을 탈출할 때 락을 해제해야 한다.
뮤텍스 락에는 Boolean available이 있는데 이 값을 통해 락 사용 여부를 확인할 수 있다.
available == true라면 acquire()을 통해 락을 획득할 수 있고 available == false로 전환된다. available == false라면 락이 해제될 때까지 block된다.release()를 통해 해제한다.acqurie()와 releas()는 반드시 원자적(atomic)으로 실행되어야 한다.락 경쟁(Lock Contention)
어떤 스레드가 락을 얻으려다
Blocking되는 경우를 락 경쟁이라고 한다.
- 높은 경쟁 상태: 여러 스레드가 동시에 락 획득을 시도하는 경우
- 낮은 경쟁 상태: 락 획득을 시도하는 스레드가 적은 경우
- 비경쟁 상태 : 락 획득 시도 시 즉시 획득되는 경우
락 경쟁이 심할수록 전체 성능이 저하된다.
락을 획득할 때까지 무한 루프를 돌며 락 획득을 기다리는 방법으로 주로 멀티 프로세서 시스템에서 락을 짧은 시간동안만 유지할 때 적합한 방식이다.
스핀 락 VS 뮤텍스 락
스핀 락과 뮤텍스 락은 속도와 CPU 사이클 측면에서 비교할 수 있다.
먼저 속도 측면에서는 뮤텍스 락은 다른 프로세스가 락을 사용 중이면Blocking되어 프로세서가 다른 작업을 수행하도록 한다. 이 과정에서 당연하게 컨텍스트 스위칭이 발생하지만 스핀 락은 무한 루프를 돌면서 CPU를 계속 점유하고 있어서 컨텍스트 스위칭이 발생하지 않는다. 즉, 뮤텍스 락이 스핀 락이 비해 속도가 느리다.
하지만 CPU 사이클 측면에서는 앞서 말했듯이 뮤텍스 락은 다른 작업을 수행할 수 있도록 하기 때문에 CPU 사이클이 낭비되지 않지만 스핀 락은 계속 CPU를 점유하므로 CPU 사이클이 낭비된다.이러한 이유로 짧은 시간에서는 스핀 락이 유리하고, 긴 시간에서는 뮤텍스 락이 유리하다.
멀티코어 환경에서는 하나의 스레드가 락을 기다리는 동안 다른 스레드가 다른 코어에서 임계 구역 작업을 끝낼 수 있기 때문에 바로 락을 획득할 수 있는 스핀 락이 더 효율적일 수 있다.
이러한 이유로 많은 운영체제는 멀티코어 환경에서 락이 짧게 유지될 경유 스핀 락을 선호한다.
학습하며 정리한 글이기 때문에 혼용된 표현 또는 잘못된 내용이 있을 수 있습니다.
만약, 발견하신 경우 댓글을 통해 알려주신다면 진심으로 감사드립니다.