병행성
은 여러 개의 프로세스나 스레드가 동시에 실행되는 것을 말합니다.
병행성을 지원하는 이유는 성능 향상과 자원 활용도 증가입니다. 병행성을 통해 여러 개의 작업을 동시에 수행할 수 있으므로, 시스템의 처리량이 증가하고 응답 시간이 감소합니다. 또한, 하나의 작업이 대기 상태에 있을 때 다른 작업을 수행할 수 있으므로, 자원의 낭비를 줄일 수 있습니다.
운영체제는 병행성(동시성)을 지원하기 위해 다양한 기법을 사용합니다.
예를 들어, 멀티태스킹, 멀티프로세싱, 멀티코어, 스케줄링, 동기화 등이 있습니다.
병행성(동시성)을 구현하는 방법은 크게 두 가지입니다.
하나는 병렬성(Parallelism)
이고, 다른 하나는 동시성(Concurrency)
입니다.
병렬성
은 여러 개의 프로세서나 코어를 사용하여 여러 개의 작업을 동시에 실행하는 것입니다.
하나의 프로세스나 작업을 여러 개의 서브프로세스나 서브작업으로 나누어 동시에 수행하는 것을 말합니다. 병렬성을 통해 시스템의 성능과 자원 활용도를 높일 수 있습니다.
운영체제에서 병렬성을 구현하는 방법에는 크게 두 가지가 있습니다.
하나는 멀티프로세싱
이고, 다른 하나는 멀티스레딩
입니다.
멀티프로세싱
이란 여러 개의 프로세서를 사용하여 여러 개의 프로세스를 동시에 수행하는 것입니다. 각 프로세서는 독립적인 메모리와 자원을 가지고 있으므로, 프로세스간의 상호작용이 적고, 오류나 장애가 발생해도 다른 프로세서에 영향을 미치지 않습니다.
멀티프로세싱
은 복잡하고 연산량이 많은 작업을 수행할 때 유리합니다. 예를 들어, 과학적 계산이나 그래픽 처리 등에 적합합니다.
멀티스레딩
이란 하나의 프로세스 내에서 여러 개의 스레드를 생성하여 동시에 수행하는 것입니다. 스레드는 프로세스의 실행 단위로, 프로세스의 메모리와 자원을 공유합니다. 따라서, 스레드간의 통신과 전환 비용이 적고, 메모리 사용량이 적습니다.
멀티스레딩
은 입출력이 많은 작업이나 사용자 인터페이스 등에 유리합니다. 예를 들어, 웹 서버나 워드 프로세서 등에 적합합니다.
동시성
은 하나의 프로세서나 코어를 사용하여 여러 개의 작업을 번갈아가며 실행하는 것입니다.
병렬성은 실제로 동시에 실행되지만, 동시성은 가상으로 동시에 실행되는 것처럼 보입니다.
동시성
은 하나의 CPU가 여러 개의 프로세스를 번갈아가며 실행하는 것으로, 실제로는 한 순간에 하나의 프로세스만 실행되지만, 인간의 시간 척도에서는 동시에 실행되는 것처럼 보입니다.
예를 들어, 컴퓨터에서 음악을 들으면서 워드 문서를 작성할 수 있는 것은 병행성(동시성) 덕분입니다.
병렬성
은 여러 개의 CPU가 여러 개의 프로세스를 동시에 실행하는 것으로, 실제로도 한 순간에 여러 개의 프로세스가 실행됩니다.
예를 들어, 멀티코어 프로세서에서 각 코어가 다른 프로세스를 처리할 수 있는 것은 병렬성 덕분입니다.
동시성
과 병렬성
은 서로 다른 개념이지만, 관련된 개념입니다.
동시성
은 하나의 CPU에서 여러 개의 프로세스를 효율적으로 처리하기 위한 방법이고, 병렬성
은 여러 개의 CPU에서 여러 개의 프로세스를 빠르게 처리하기 위한 방법입니다.
또한, 동시성
은 단일 시스템에서 구현할 수 있지만, 병렬성
은 분산 시스템에서도 구현할 수 있습니다.
프로세스 동기화
란 여러 개의 프로세스가 공유 자원에 접근할 때, 서로의 작업을 조율하고 협력하는 것을 말합니다.
프로세스 동기화의 목적은 데이터의 일관성과 병렬성을 보장하는 것입니다.
데이터의 일관성
이란 공유 자원이 여러 프로세스에 의해 동시에 변경되는 경우, 올바른 결과를 유지하는 것을 의미합니다.
병렬성
이란 여러 프로세스가 동시에 수행되면서 시스템의 성능을 향상시키는 것을 의미합니다.
가장 기본적인 기법은 임계 구역(critical section)
이라고 부르는 코드 영역을 정의하고, 이 영역에 들어가는 프로세스는 상호 배제(mutual exclusion)
를 통해 다른 프로세스와 공유 자원의 접근을 막는 것입니다.
임계 구역 문제를 해결하기 위해 세마포어(semaphore)
, 모니터(monitor)
, 메시지 전달(message passing)
등의 동기화 도구
를 사용할 수 있습니다.
임계 구역(critical section)
이란 여러 프로세스나 스레드가 동시에 접근하면 안 되는 공유 자원을 사용하는 코드 영역을 말합니다.
예를 들어, 두 개의 스레드가 같은 변수를 증가시키는 연산을 수행한다고 가정해봅시다. 이 때, 한 스레드가 변수를 읽고 증가시키기 전에 다른 스레드가 변수를 읽고 증가시키면, 원래 의도와 다르게 변수의 값이 1만 증가하게 됩니다.
이러한 문제를 해결하기 위해서는 임계 구역(critical section)을 보호
하는 방법이 필요합니다.
임계 구역(critical section)을 보호하는 방법은 크게 두 가지로 나눌 수 있습니다.
인터럽트를 비활성화하거나 특수한 명령어를 사용하여 원자적인 연산을 수행하는 것입니다.
이 방법은 간단하고 효율적이지만, 운영체제의 개입이 없으므로 교착상태나 기아상태 등의 문제가 발생할 수 있습니다. 또한, 멀티 프로세서 환경에서는 적용하기 어렵습니다.
운영체제가 제공하는 동기화 기법을 사용하는 것입니다.
이 방법은 세마포어
, 뮤텍스
, 모니터
등의 도구를 이용하여 임계 구역(critical section)에 들어갈 수 있는 프로세스나 스레드의 수를 제한하거나 순서를 정하는 것입니다.
이 방법은 하드웨어적인 방법보다 복잡하고 비용이 많이 들지만, 교착상태나 기아상태 등의 문제를 예방하거나 해결할 수 있습니다. 또한, 멀티 프로세서 환경에서도 적용할 수 있습니다.
Race Condition
이란 여러 개의 프로세스나 스레드가 공유 자원에 동시에 접근하려고 할 때 발생하는 문제입니다.
예를 들어, 두 개의 스레드가 같은 변수를 읽고 쓰려고 할 때, 어떤 스레드가 먼저 실행되느냐에 따라 변수의 값이 달라질 수 있습니다.
이렇게 Race Condition
이 발생하면 프로그램의 결과가 예측할 수 없게 되고, 오류나 버그를 유발할 수 있습니다.
공유 자원에 접근하는 부분을 임계 영역(Critical Section)으로 정의하고, 임계 영역에 들어가는 스레드는 다른 스레드들이 접근하지 못하도록 락(Lock)을 거는 것입니다.
임계 영역을 설정하기 위해 여러 가지 도구들이 있습니다.
예를 들어, 세마포어(Semaphore)
, 뮤텍스(Mutex)
, 모니터(Monitor)
등이 있습니다.
이렇게 하면 한 번에 하나의 스레드만 공유 자원을 사용할 수 있으므로 Race Condition을 막을 수 있습니다.
원자적 연산이란 중간에 다른 연산이 끼어들지 못하도록 보장되는 연산을 말합니다.
예를 들어, 증감 연산자(++, --)는 원자적 연산으로 구현되어 있습니다. 따라서 증감 연산자를 사용하면 Race Condition을 방지할 수 있습니다.
Mutual Exclusion
이란 여러 프로세스나 스레드가 동시에 공유 자원에 접근하는 것을 방지하는 기법을 의미합니다.
예를 들어, 두 개의 프로세스가 같은 파일을 수정하려고 할 때, 한 프로세스가 파일을 사용하고 있으면 다른 프로세스는 기다려야 합니다.
이렇게 하면 데이터의 일관성과 정확성을 보장할 수 있습니다.
Mutual Exclusion을 구현하는 방법은 여러 가지가 있습니다.
가장 간단한 방법은 Locking
이라고 하는데, 공유 자원에 접근하기 전에 잠금을 걸고, 접근이 끝나면 잠금을 해제하는 것입니다.
이 방법은 쉽고 직관적이지만, 잠금이 오래 걸리거나 깨지지 않으면 교착 상태(Deadlock)이 발생할 수 있습니다.
대표적인 방법으로는 세마포어(Semaphore)
, 뮤텍스(Mutex)
, 모니터(Monitor)
등이 있습니다.
뮤텍스
는 lock과 unlock으로 구성되며, 하나의 스레드가 뮤텍스를 lock하면 다른 스레드는 뮤텍스가 unlock될 때까지 기다려야 합니다. 이렇게 함으로써 공유 자원의 일관성을 유지할 수 있습니다.
뮤텍스는 다음과 같은 특징을 가집니다.
뮤텍스
는 바이너리 세마포어와 유사하지만, 뮤텍스는 lock한 스레드만이 unlock할 수 있습니다.
세마포어는 다른 스레드가 unlock 할 수 있습니다.
뮤텍스
는 재귀적으로 lock할 수 있습니다.
즉, 같은 스레드가 여러 번 lock하면, 같은 횟수만큼 unlock해야 합니다.
뮤텍스
는 우선순위 역전 현상을 방지하기 위한 우선순위 상속 기능을 제공합니다.
우선순위 역전이란, 낮은 우선순위의 스레드가 뮤텍스를 잡고 있을 때, 높은 우선순위의 스레드가 기다리게 되는 현상입니다. 이때, 낮은 우선순위의 스레드는 높은 우선순위의 스레드의 우선순위를 상속받아 실행됩니다.
데드락(deadlock)을 피하기 위해서는 여러 개의 뮤텍스를 lock할 때에는 항상 일정한 순서로 lock하고 반대 순서로 unlock해야 합니다.
불필요한 오버헤드를 줄이기 위해서는, 공유 자원에 접근하는 시간을 최소화하고 불필요한 lock과 unlock을 피해야 합니다.
성능을 향상시키기 위해서는, 공유 자원을 세분화하고 필요한 부분만 lock하도록 해야 합니다.
세마포어
는 정수형 변수로서 공유 자원에 접근할 수 있는 프로세스나 스레드의 수를 나타냅니다.
세마포어의 값
이 0보다 크면 공유 자원에 접근할 수 있고, 0이면 접근할 수 없습니다.
세마포어는 두 가지 연산으로 조작할 수 있습니다.
wait 연산
은 세마포어의 값을 1 감소시키고, signal 연산
은 세마포어의 값을 1 증가시킵니다.
wait 연산
은 공유 자원에 접근하기 전에 수행되고, signal 연산
은 공유 자원에서 빠져나온 후에 수행됩니다.
세마포어는 여러 개의 공유 자원에 대해 Mutual Exclusion을 구현할 수 있지만, 교착 상태(Deadlock)나 기아 상태(Starvation)가 발생할 가능성이 있습니다.
세마포어는 크게 이진 세마포어
와 카운팅 세마포어
로 나뉩니다.
이진 세마포어
는 값이 0 또는 1만 가질 수 있는 세마포어로, 상호 배제를 구현하는 데 사용됩니다.
예를 들어, 두 개의 프로세스가 한 개의 파일을 공유하고자 할 때, 이진 세마포어를 사용하여 파일에 대한 접근을 제한할 수 있습니다.
카운팅 세마포어
는 값이 0 이상의 정수를 가질 수 있는 세마포어로, 한 번에 여러 개의 자원을 할당하거나 제한하는 데 사용됩니다.
예를 들어, 10개의 프로세스가 5개의 프린터를 공유하고자 할 때, 카운팅 세마포어를 사용하여 프린터에 대한 접근을 제한할 수 있습니다.
뮤텍스
는 한 번에 하나의 스레드만 공유 자원에 접근할 수 있도록 보장합니다.
뮤텍스는 소유권 개념이 있어서 뮤텍스를 획득한 스레드만이 뮤텍스를 해제할 수 있습니다.
또한, 뮤텍스는 재귀적으로 획득할 수 있습니다.
즉, 같은 스레드가 이미 획득한 뮤텍스를 다시 획득할 수 있으며, 이때는 반드시 획득한 횟수만큼 해제해야 합니다.
이진 세마포어
는 뮤텍스와 비슷하게 한 번에 하나의 스레드만 공유 자원에 접근할 수 있도록 합니다.
그러나 이진 세마포어는 소유권 개념이 없습니다. 즉, 어떤 스레드든지 이진 세마포어를 획득하거나 해제할 수 있습니다.
또한, 이진 세마포어는 재귀적으로 획득할 수 없습니다. 즉, 같은 스레드가 이미 획득한 이진 세마포어를 다시 획득하면 데드락(Deadlock)이 발생합니다.
요약하면, 뮤텍스와 이진 세마포어의 차이점은 다음과 같습니다.
뮤텍스는 소유권 개념이 있고, 이진 세마포어는 없다.
뮤텍스는 재귀적으로 획득할 수 있고, 이진 세마포어는 없다.
뮤텍스는 뮤텍스를 획득한 스레드만이 해제할 수 있고, 이진 세마포어는 어떤 스레드든지 해제할 수 있다.
상호배제만 필요하다면
뮤텍스
를, 작업간의 실행 순서 동기화가 필요하다면세마포어
를 사용하면 됩니다.
모니터
는 프로그래밍 언어 수준에서 동기화 기능을 제공해줍니다.
모니터
는 공유 자원과 그것을 조작하는 함수들을 하나의 모듈로 묶은 것입니다.
모니터 내부에서는 한 번에 하나의 프로세스나 스레드만이 실행될 수 있으므로 Mutual Exclusion이 보장됩니다.
모니터
는 조건 변수(Condition Variable)
라는 특수한 변수를 사용하여 프로세스나 스레드간의 동기화를 할 수 있습니다.
조건 변수는 wait
와 signal
두 가지 연산으로 조작할 수 있습니다.
wait 연산
은 현재 실행중인 프로세스나 스레드를 대기 상태로 만들고, signal 연산
은 대기 상태인 프로세스나 스레드 중 하나를 깨웁니다.
모니터
내부에는 단 하나의 프로세스만 존재할 수 있기 때문에 동기화 문제가 자연스럽게 해결됩니다.
모니터
를 사용하면 프로그래머는 간편하게 동기화를 수행할 수 있습니다.
단지 모니터 진입 가능 여부에 따라 큐에 프로세스를 대기 시키고, 깨우는 것만 구현하면 됩니다.
데드락
이란 두 개 이상의 프로세스가 서로가 가지고 있는 자원을 기다리면서 영원히 대기 상태에 빠지는 현상을 말합니다.
예를 들어, 프로세스 A가 파일 1을 열고 있고, 프로세스 B가 파일 2를 열고 있습니다. 이때, 프로세스 A가 파일 2를 열려고 하고, 프로세스 B가 파일 1을 열려고 하면, 둘 다 상대방이 자원을 해제할 때까지 기다려야 합니다.
이런 상황에서는 두 프로세스 모두 작업을 진행할 수 없게 되므로, 데드락이 발생한 것입니다.
상호 배제(Mutual Exclusion)
한 번에 한 개의 프로세스만 자원을 사용할 수 있습니다.
점유 대기(Hold and Wait)
자원을 가지고 있는 프로세스가 다른 자원을 기다리고 있습니다.
비선점(No Preemption)
자원을 가진 프로세스는 자신이 사용을 마칠 때까지 자원을 뺏길 수 없습니다.
순환 대기(Circular Wait)
여러 개의 프로세스가 순환적으로 자원을 기다리고 있습니다.
데드락 발생 조건은 상호 배제(mutual exclusion), 점유와 대기(hold and wait), 비선점(non-preemption), 순환 대기(circular wait)의 네 가지입니다.
이 중 하나라도 만족하지 않으면 데드락은 발생하지 않습니다.
상호 배제 조건 제거
자원을 공유 가능하게 만듭니다.
점유와 대기 조건 제거
프로세스가 자원을 요청하기 전에 모든 자원을 할당받게 만듭니다.
비선점 조건 제거
이미 할당된 자원을 강제로 회수합니다.
순환 대기 조건 제거
자원에 순서를 부여하여 순서대로 요청하도록 합니다.
하지만 이러한 방법들은 자원의 활용도를 낮추거나 오버헤드를 증가시키는 단점이 있습니다.
이 방법은 자원 할당 그래프(resource allocation graph)나 은행원 알고리즘(banker's algorithm)과 같은 수학적 모델을 사용하여 시스템의 상태를 분석하고, 데드락이 발생할 가능성이 있는 요청은 거부하거나 연기하는 방식입니다.
이 방법은 데드락이 발생하지 않음을 보장할 수 있지만, 시스템의 복잡도와 부담을 증가시키는 단점이 있습니다.
이 방법은 주기적으로 또는 특정 조건에서 데드락 탐지 알고리즘(deadlock detection algorithm)을 실행하여 시스템에 데드락이 존재하는지 확인하고, 데드락이 발견되면 복구 알고리즘(recovery algorithm)을 통해 데드락에 연루된 프로세스를 종료하거나 할당된 자원을 해제하는 방식입니다.
이 방법은 시스템의 자원 활용도를 높일 수 있지만, 데드락이 발생할 때까지 기다려야 하고, 복구 과정에서 작업 손실이나 비용 증가 등의 문제가 발생할 수 있는 단점이 있습니다.