여러 프로세스 또는 스레드에서 하나의 공유 자원에 접근하는 경우가 있는데, 이때 자원에 접근하는 순서에 따라 결과 값이 달라질 수 있다. 이러한 현상을 공유 자원에 동시에 접근해 경쟁하는 상태라고 해서 경쟁 상태(race condition)라고 한다.
임계 영역(Critical Section)이란 여러 프로세스나 스레드가 동시에 실행될 때, 공유 자원에 접근하는 코드의 일부를 의미합니다. 임계 영역 문제는 이러한 공유 자원에 대한 동시 접근을 제어하여 데이터의 일관성과 무결성을 유지해야 한다.
임계 영역에 여러 접근이 동시에 발생하는 것을 방지하려면 다음 3가지 조건이 충족되어야 한다.
임계 영역에 접근하지 못한 프로세스는 락을 얻기 위해 기다리는 동안 락이 풀렸는지 반복문을 돌면서 확인한다.
이를 빠쁜 대기(busy waiting)의 한 종류인 스핀락이라고 한다.
즉, 스핀락(spinlock)은 락을 얻기 위해 프로세스가 반복문을 돌면서 기다리는 것을 의미한다. 프로세스가 대기 상태가 되지 않고 반복문을 돌면서 자원의 사용 가능 여부를 확인하므로 프로세스가 빠르게 교체될 수 있다.
임계 영역에 접근할 수 있는 키 n개를 지정하고 이 중 하나를 가진 프로세스만이 임계 영역에 접근하게 하는 방식이다. 이 방식은 공유 자원에 접근한 프로세스가 접근을 해제하면 다른 프로세스가 접근할 수 있도록 신호를 보낸다고 해서 시그널링 메커니즘 이라고도 한다.
교착상태(Deadlock)는 멀티테스킹 환경에서 발생할 수 있는 문제로, 두 개 이상의 프로세스나 스레드가 서로의 작업이 끝나기를 무한정 기다리고 있어서, 그 어느 것도 진행될 수 없는 상태를 말합니다. 즉, 각각의 프로세스가 서로가 가진 자원을 요구하지만, 아무도 자원을 양보하지 않아 발생하는 상태입니다. 이는 시스템의 성능 저하나 시스템 정지 같은 심각한 문제를 초래할 수 있습니다.
교착상태가 발생하기 위해서는 다음 네 가지 조건이 모두 충족되어야 합니다
교착 상태를 막으려면 앞의 4가지 필요 충분 조건중에서 한 가지를 제거하면 된다.
스레드 안전(thread safety)이란, 여러 스레드가 동시에 같은 데이터에 접근하더라도 프로그램의 실행 결과가 변경되지 않도록 보장하는 것을 말합니다. 멀티스레딩 환경에서는 여러 스레드가 동시에 데이터를 읽고 쓰려고 할 때, 예상치 못한 결과나 데이터 손상이 발생할 수 있습니다.
스레드 안전성을 확보하는 방법은 여러 가지가 있으나, 주로 다음과 같은 기법들이 사용됩니다:
1. 뮤텍스(Mutex) 및 세마포어(Semaphore): 공유 자원에 대한 접근을 제어하기 위해 뮤텍스나 세마포어 같은 동기화 메커니즘을 사용합니다. 이러한 메커니즘은 한 번에 하나의 스레드만 공유 자원에 접근할 수 있도록 합니다.
2. 원자적 연산(Atomic Operations): 원자적 연산을 사용하면 복잡한 연산이더라도 중간에 다른 스레드에 의해 방해받지 않고 '분할 불가능한' 하나의 단위로 실행됩니다.
3. 불변 객체(Immutable Objects): 데이터가 생성된 후에는 변경되지 않는 객체를 사용함으로써 스레드 안전성을 확보할 수 있습니다. 객체가 불변이면 여러 스레드가 동시에 객체를 참조해도 문제가 발생하지 않습니다.
4. 스레드 로컬 저장소(Thread Local Storage): 각 스레드가 데이터의 자체 복사본을 가지고 있어서 다른 스레드와 데이터를 공유하지 않는 방식입니다. 이 방법을 사용하면 스레드 간의 데이터 충돌을 방지할 수 있습니다.
IPC(Inter-Process Communication)는 서로 다른 프로세스 간에 데이터를 교환하는 메커니즘입니다. 이는 운영 체제가 제공하는 기능으로, 프로세스들이 서로 정보를 공유하고, 동기화하며, 협업을 할 수 있게 해줍니다.
IPC 메커니즘에는 여러가지가 있습니다.
1. 공유 메모리(Shared Memory): 두 프로세스가 시스템의 동일한 메모리 영역을 공유하여 데이터를 교환할 수 있게 합니다. 가장 빠른 데이터 교환 방식 중 하나입니다.
2. 소켓(Sockets): 네트워크를 통한 통신을 가능하게 하는 인터페이스로, 일반적으로 서로 다른 호스트에서 실행되는 프로세스 간 통신에 사용됩니다.
3. 파이프(Pipes): 일반적으로 한 프로세스의 출력을 다른 프로세스의 입력으로 연결하는데 사용됩니다. 단방향 통신 채널입니다. 따라서 양방향 통신을 하려면 읽기 파이프와 쓰기 파이프를 각각 생성해야 합니다.
4. 메시지 큐(Message Queues): 프로세스들이 메시지의 형태로 데이터를 교환할 수 있게 해주며, 비동기적 통신을 지원합니다.
좀비 프로세스는 종료되었지만, 부모 프로세스가 종료 상태를 회수하지 않아 프로세스 테이블에서 완전히 제거되지 않은 상태의 프로세스를 말합니다. 이런 프로세스는 시스템의 리소스를 거의 사용하지 않지만, 프로세스 테이블에서 공간을 차지하여 시스템의 성능에 영향을 줄 수 있습니다. 부모 프로세스가 wait() 시스템 콜을 사용하여 자식 프로세스의 종료 상태를 회수하면 좀비 프로세스는 시스템에서 완전히 제거됩니다.
고아 프로세스는 부모 프로세스가 종료되었지만, 자신은 아직 종료되지 않은 프로세스를 말합니다. 부모 프로세스가 종료될 때, 시스템은 고아가 된 모든 자식 프로세스를 init 프로세스(프로세스 ID가 1인 프로세스)의 자식으로 채택하여, 이후에 init 프로세스가 자식 프로세스의 종료 상태를 회수합니다. 이 과정을 통해 고아 프로세스는 시스템의 리소스를 정상적으로 해제하고 종료될 수 있습니다.