공유된 자원에 여러 프로세스가 동시에 접근하면서 문제가 발생할 수 있습니다.
이때 공유된 자원의 데이터는 한 번에 하나의 프로세스만 접근할 수 있도록 제한을 둬야 합니다.
이를 위하여 고안된 것이 바로 Semaphore 세마포어입니다.
여러 프로세스가 데이터를 공유하며 수행될 때, 각 프로세스에서 공유 데이터를 접근하는 프로그램 코드 부분
공유 데이터를 여러 프로세스가 동시에 접근할 때 잘못된 결과를 만들 수 있기 때문에, 한 프로세스가 임계 구역을 수행할 때는 다른 프로세스가 접근하지 못하도록 해야 한다.
현재 공유자원에 접근할 수 있는 스레드, 프로세스의 수를 나타내는 값을 두어 상호배제를 달성하는 기법입니다. 세마포어는 Signaling 메커니즘이라는 점에서 뮤텍스와 다릅니다. 세마포어는 락을 걸지 않은 쓰레드도 Signal을 보내 락을 해제할 수 있다는 점에서, wait 함수를 호출한 쓰레드만이 signal 함수를 호출할 수 있는 뮤텍스와 다릅니다.
세마포어는 동기화를 위해 wait와 signal이라는 2개의 atomic operations를 사용합니다.
wait를 호출하면 세마포어의 카운트를 1줄이고, 세마포어의 카운트가 0보다 작거나 같아질 경우에 락이 실행됩니다.
struct semaphore { int count; queueType queue; }; void semWait (semaphore s) { s.count--; if (s.count <= 0) { // 락이 걸리고 공유 자원에 접근할 수 없음 } } void semSignal (semaphore s) { s.count++; if (s.count <= 0) { // 아직 락에 걸려 대기중인 프로세스가 있음 } }
세마포어의 카운트가 0보다 작거나 같아져 동기화가 실행된 상황에, 다른 쓰레드가 signal 함수를 호출하면 세마포어의 카운트가 1증가하고, 해당 쓰레드는 락에서 나올 수 있습니다.
세마포어는 크게 Counting Semaphores, Binary Semaphore 2종류가 있다. 카운팅 세마포어는 세마포어의 카운트가 양의 정수값을 가지며, 설정한 값만큼 스레드를 허용하고 그 이상의 쓰레드가 자원에 접근하면 락이 실행됩니다. 바이너리 세마포어는 세마포어의 카운트가 1이며 Mutex처럼 사용될 수 있습니다.(뮤텍스는 절대로 세마포어처럼 사용될 수 없습니다.)
뮤텍스는 자원에 대한 접근을 동기화하기 위해 사용되는 상호배제 기술입니다. 이것은 프로그램이 시작될 때 고유한 이름으로 생성됩니다. 이진 세마포어(Binary Semaphore)와 같이 초기값을 1, 0으로 가집니다. 뮤텍스는 1개의 락만을 갖는 Locking 메커니즘으로 오직 하나의 쓰레드만이 동일한 시점에 뮤텍스를 얻어 임계 영역(Critical Section)에 들어올 수 있습니다. 임계 영역에 들어갈 때 락(lock)을 걸어 다른 프로세스(or 스레드)가 접근하지 못하도록 하고, 임계 영역에서 나와 해당 락을 해제(unlock)합니다. 오직 이 쓰레드만이 임계 영역에서 나갈 때 뮤텍스를 해제할 수 있습니다.
wait (mutex); ... Critical Section ... signal (mutex);
뮤텍스는 Locking 메커니즘으로 락을 걸은 스레드만이 임계 영역을 나갈때 락을 해제할 수 있습니다. 하지만 세마포어는 Signaling 메커니즘으로 락을 걸지 않은 스레드도 signal을 사용해 락을 해제할 수 있습니다. 또한, 세마포어의 카운트를 1로 설정하면 뮤텍스처럼 활용할 수 있습니다. 반면 뮤텍스는 절대로 세마포어처럼 사용될 수 없습니다.
두 기법 모두 완벽한 기법은 아닙니다. 이 기법들을 쓰더라도 데이터 무결성을 보장할 수 없으며 데드락이 발생할 수도 있습니다. 하지만 상호배제를 위한 기본적인 기법이며 여기에 좀 더 복잡한 매커니즘을 적용해 멋있게 동작하는 프로그램을 개발할 수 있습니다.