- 임계구역은 둘 이상의 프로세스에 의해서 동시에 접근하면 안되는 공유 자원(임계자원)에 접근하는 영역(코드 영역)입니다.
- 즉, 프로그램에서 공유데이터(임계자원)를 이용하는 부분입니다.
- 이러한 구역에서 공유 자원은 공동으로 이용되기 때문에 프로세스/스레드가 어떤 순서로 어떻게 데이터를 읽거나 쓰느냐에 따라 결과를 예측하기 어렵다는 문제가 있습니다.
- 임계구역의 예시로는, 다수의 프로세서가 접근 가능한 영역이면서 한 순간에 하나의 프로세서만 사용할 수 있는 영역이 있습니다.
- 세가지 조건을 만족할 때 임계구역 문제를 방지할 수 있습니다.
(1) 상호배제 - 임계영역에는 한번에 하나의 프로세스/스레드만 접근할 수 있습니다.
(2) 진행(progress) - 임계영역에 프로세스가 없다면, 적절한 프로세스를 임계영역에 진입할 수 있게 선택해야 합니다.
(3) 한정 대기(bounded waiting) - 한 프로세스가 임계영역 진입 요청을 한 후 수락되기까지 다른 프로세스가 임계영역에 진입할 수 있는 횟수를 제한해야 합니다. - 이는 다른 프로세스가 영원히 임계영역에 들어가지 못하는 기아 상태를 방지하기 위함입니다.
- 임계구역 문제를 해결하기 위해선 한번에 하나의 프로세스만 임계구역을 이용하게끔 보장해야 합니다. 임계 영역의 동시접근을 해결하기 위한 방법으로 OS supported SW Solutions 인 Spinlock, Semaphore, Monitor기법이 존재합니다.
(1) Spinlock
-
두 스레드가 하나의 임계영역에 접근할때, 하나만 접근 가능한 상황에서 Thread 0이 임계영역을 점유중이고 lock을 걸어놓은 상태일때, Thread 1은 접근이 불가능하고 기다려야 합니다.
-
spinlock에서는 기다리는 과정에서
Busy waiting
이라는 방법을 사용하는데, Thread 0이 임계영역을 다 사용했는지의 여부를 Thread 1이 계속 확인하는 것입니다. -
Busy waiting
은 컴퓨터 리소스가 크게 낭비된다는 단점이 있습니다. -
CPU가 여러개인 환경(멀티 프로세서)에서만 사용 가능합니다. 싱글 코어에서 스핀락은 사용하지 못합니다. (CPU가 하나면 Busy-waiting 하나밖에 못하기 때문입니다.)
-
Spin Lock의 개발 컨셉은 "lock - unlock 사이에 약간의 시간만 기다리면 바로 lock이 풀리는데 굳이 context switch를 해서 overhead를 발생시킬 필요가 있을까"에서 출발했습니다.
-
"lock - unlock"과정이 아주 짧을 때 유용합니다. context switch하는 시간보다 lock의 주기가 더 짧으면 Spin Lock을 사용합니다.
-
하지만 Critical Section이 매우 크거나 프로세스의 처리 속도가 느리다면 Spin Lock은 대기하고 있는 프로세스들이 CPU에 주는 부담이 굉장히 커집니다.
(2) Mutex
-
Mutual Exclusion 의 줄임말로 상호 배제를 가능하게 합니다.
-
뮤텍스는 lock이기에 실행할때 잠궜다가 다 쓰고나면 열어둔 후, 대기하던 다음 프로세스를 깨워주며
Busy waiting
단점을 보완합니다. -
주의점으로는 잘못 설계할 경우 lock 권한을 가진 스레드가 항상 점유해버리므로 starvation 문제가 일어날 수 있습니다.
-
많은 lock을 사용하게 될 경우, deadlock 발생가능성이 높습니다.
-
<작동원리> - 1. 프로세스가 Critical Section에 들어가려면 먼저 "Lock"을 획득합니다. : acquire() - 2. Critical Section에서 빠져나올 때는 "Lock"을 반환해야 합니다. : release() => acquire() & release()는 둘 다 "Atomic"하게 동작합니다.
-
추가적으로, 뮤텍스가 스핀락보다 항상 좋다고 말할 수 없습니다. - 1. 멀티코어 환경이며 (스핀락은 싱글코어에선 작동할 수 없습니다.) - 2. 임계구역에서의 작업이 컨텍스트 스위칭보다 더 빨리 끝난다면 오히려 스핀락이 더 유리합니다.
"Busy Waiting" vs "Block & WakeUp"
- 일반적인 경우에는 Block & WakUp이 더 효율적입니다.
- Block & WakUp 장점
-
- 쓸데없이 CPU를 점유하면서 기다리지 않습니다.
-
- 먼저 Resoruce 여부를 확인하고 여분이 없으면 알아서 CPU 반납하고 block합니다.
-
- 전체적으로 CPU 처리율이 향상됩니다.
-
- Block & WakUp 단점
-
- overhead
- Block은 ready -> block 과정이고 WakeUp은 block -> ready 과정이고 이 때 당연히 overhead가 발생
-
1) Critical Section이 매우 짧은 경우 - Block & WakeUp의 overhead가 "Busy Waiting"의 CPU 점유보다 좋지 않습니다. => Spin Lock을 선택합니다.
2) Critical Section이 긴 경우 - "Busy Waiting"은 결국 CPU를 점유하고 CPU를 생산적으로 활용하지 않기 때문에 CPU의 낭비가 심합니다. 따라서 Critical Section이 긴 경우 "Block & WakeUp"을 통해서 CPU의 처리율을 향상시키는 것이 더 좋습니다. => Mutex, Semaphore을 선택합니다.
(3) Semaphore
- 세마포어는 spinlock의 단점인 busy waiting을 극복하기 위해 wait()와 signal() 메소드를 활용합니다.
- Wait 상태인 스레드는 대기 큐에 들어가서 기다리며 while 문을 반복 실행하지 않으므로 리소스 낭비가 없습니다.
- 임계영역을 점유중이던 스레드는 작업이 끝나는대로 signal()을 통해 lock을 반환하므로써 대기 큐에 존재하던 스레드를 깨워 준비 큐로 이동시킵니다.
-
크게 binary semaphore와 counting semaphore 두가지로 구분됩니다.
- (3-1) Binary semaphore
- S가 0 과 1 두가지의 값만 존재합니다.
- 하나의 임계영역은 하나의 스레드만이 점유 가능합니다.
- (3-2) Counting semaphore
- S가 양의 정수라서 해당 정수 값만큼의 스레드들이 동시에 접근 가능합니다.
- Counting Semaphore에서의 S는 "Available한 Resource의 수"입니다.
- 따라서 여러 스레드에 접근 기회를 제공합니다.
- (3-1) Binary semaphore
-
세마포어와 스핀락 비교 <리소스의 낭비 비교>
- spinlock은 loop를 통해 스레드로 하여금 끊임없이 임계영역의 사용 가능 여부를 확인하도록 강제합니다
- 반면, semaphore는 바로 접근이 불가능한 스레드는 sleep 시키고 사용가능한 상황일때 awaken up 시켜줍니다. => semaphore에서는 리소스의 낭비를 줄일 수 있습니다
<lock 구현과 대기 시간 비교>
- 그러나 spinlock은 구현이 간단하며 lock을 짧은 시간동안만 점유할 경우 에는 오히려 좋습니다.
- semaphore는 구현이 어렵지만 오랜 시간을 대기해야하는 상황에서 효율적입니다.
(4) Monitor (JAVA - synchronized)
-
스레드가 상호 배제와 협동성을 가질 수 있도록 하는 동기화 메커니즘입니다.
-
스레드가 어떻게 자원에 접근하는지 감시합니다.
-
Java 에서는 Synchronized 키워드를 사용해 Monitor를 구현합니다.
-
모니터는 entry set, wait set, 임계영역 이렇게 세부분으로 구분됩니다.
-
임계영역은 synchronized 키워드가 선언된 부분으로 스레드가 접근하고 싶은 영역입니다. - wait set은 스레드가 대기하는 곳입니다. - entry set은 스레드가 입장하는 곳입니다. - wait() , notify() 메소드를 사용합니다.
-
작동 원리
-
- 스레드가 entry set에 들어가서 스케줄러를 기다립니다.
-
- 스케줄러가 스레드를 임계영역에 들여보내줍니다.
-
- 작업이 완료될 수도 있지만 때로는 wait set에서 대기를 해야합니다.
-
- 다시 차례가 오면 임계영역에 접근하여 작업을 완료합니다.
- JVM의 스케줄러는 우선순위를 기반으로 한 스케줄링 알고리즘을 사용합니다.
- 동일한 우선순위를 가진 경우에는 FIFO 스케줄링으로 해결합니다.
- 세마포어는 세마포어 값의 범위에 따라서 계수 세마포어(Counting Semaphore)와 이진 세마포어(Binary Semaphore)로 구분됩니다.
- 생산자-소비자 문제를 해결하기 위해 사용돠며 S가 0이상의 정수 값을 갖습니다.
- 여러 프로세스, 여러 스레드의 접근을 허용하는 것은 계수 세마포어라고 지칭합니다.
- Binary Semaphore는 세마포어에서 사용하는 변수가 0혹은 1인 세마포어입니다. 이는 한 임계 구역에 하나의 프로세스 / 하나의 스레드만 접근을 허용합니다.
- 상호배제나 프로세스 동기화의 목적으로 사용,2 S가 0과 1 두 값만 갖습니다.
- 둘은 명확한 차이가 존재합니다.
- (1) lock을 해제할 수 있는 권한의 차이가 있습니다. 뮤텍스는 무조건 lock을 건 주체만 lock을 해제할 수 있습니다. 반면 세마포어는 lock을 걸지 않은 주체도 lock을 해제할 수 있습니다.
- (2) mutex는
priority inheritance
특성을 사용하나, 세마포어는 사용하지 않습니다. - priority inheritance : lock을 획득한 프로세스/스레드의 우선순위를 높여주는 것입니다. - mutex는 lock을 획득할 수 있는 존재를 예측할 수 있어, 이 속성을 사용가능하나, 세마포어는 예측이 어려워 이를 사용하지 않습니다. - (3)
binary semaphore
는signaling mechanism
이고mutex
는locking mechanism
이라는 차이가 존재합니다.
-
- 상호배제 문제
- spinlock은 물건이 없으면 Busy waiting을 했으나, 세마포어는 대기실(ready queue)에서 기다리게 하고 lock을 얻을 기회가 오면 이를 할당해주면서 상호배제 문제를 해결합니다.
-
- 프로세스 동기화 프로세스들의 실행 순서를 맞춰줍니다.
-
-
PIPE
-
Message Queue
-
Shared Memory - 시스템 상의 공유 메모리를 통해 통신합니다. - 일정한 크기의 메모리를 프로세스간에 공유하는 구조이며 공유 메모리는 커널에서 관리됩니다.
- IPC through Shared memory 를 통해 여러 프로세스가 하나의 메모리를 공유할 수 있습니다. - 이는 pipe, message queue 방식과 달리 매개체를 경유하지 않기에 IPC 기법 중 가장 빠릅니다. - 즉 메모리 자체를 공유하기에 불필요한 데이터 복사의 오버헤드가 발생하지 않는다는 장점이 있습니다. - 생성된 공유메모리는 커널에 의해 관리됩니다. - 같은 메모리 영역을 여러 프로세스가 공유하기에 적절한 동기화가 필요하다는 점이 존재합니다.
- 소켓 : 네트워크 소켓은 컴퓨터 네트워크를 경유하는 프로세스 간 통신의 종착점을 가리킵니다. 즉, 프로그램이 네트워크에서 데이터를 통신할 수 있도록 연결해주는 역할을 수행합니다.
- 하드웨어 명령어로는, test_and_set() & compare_and_swap() 이 존재합니다.
- 많은 기계들은 한 워드(word)의 내용을 검사하고 변경하거나, 두 워드의 내용을 원자적으로(atomically) 교환(swap)할 수 있는, 즉 인터럽트 되지 않는 하나의 단위로서, 특별한 하드웨어 명령어들을 제공합니다.
- 이와 같은 하드웨어 명령어들을 통해 임계구역 문제를 해결할 수 있습니다.
- HW 명령어는 데이터의
접근 → 연산 → 연산 결과 저장
의 과정을 한 번에 수행할 수 있는(ATOMIC 실행,원자적 실행) HW 지원을 해줌으로써 실행 중 인터럽트를 받지 않아서 preemption 되지 않습니다. => 즉, 원자적으로 수행한다는 것은 인터럽트 되지 않고 수행된다는 것입니다.
(1) test_and_set()
- test_and_set(lock) 명령어는 lock의 값을 읽고(복사 후 반환), lock을 True로 설정하는 일을 한 번에(원자적으로) 수행합니다.
(2) compare_and_swap()
- 마찬가지로 원자적으로 특정한 연산을 수행해줍니다. test_and_set 과는 다르게 3개의 매개 변수를 사용합니다. value가 expected와 같다면, value의 값을 new_value로 설정하는 일을 한 번에 수행하며 결과적으로 본다면 test_and_set과 동일하게 작동합니다.
(3)원자적 변수 (Atomic Variables)
- 원자적 변수는 compare_and_swap 명령어를 다시 원자적인 연산으로 구현하여 사용하는 도구입니다.
- 속도가 느립니다.
- 구현이 복잡합니다. (<-> HW 명령어는 구현이 간단합니다.)
- 상호배제 실행 중 preemption 문제가 있습니다. (<-> HW는 ATOMICALLY 실행 지원을 해줌으로써 실행 중 인터럽트를 받지 않기에, preemption 되지 않습니다.)
-
Peterson’s Solution
이 존재합니다. -
이는 임계 구역과 나머지 구역을 번갈아 가며 실행하는 두 개의 프로세스가 있는 경우로 한정해서 생각합니다.
-
두 프로세스가 두 개의 데이터 항목을 공유하여 공유메모리를 통해 문제를 해결합니다.
-
flag와 turn 변수를 사용하여 프로세스가 임계구역에 들어가려고 하는 것을 구분합니다.
- flag 값이 true 이면 프로세스가 임계구역에 들어가려고 하는 것을 나타냅니다.
-
이 알고리즘은 임계구역 문제 해결의 3가지 조건을 모두 만족합니다.
-
다만, 고전적인 소프트웨어 기반의 해결책이기에, 현대의 컴퓨터 아키텍처에서 작동하지 않을 수도 있습니다.
busy waiting
입니다. 다만 이는 CPU를 효율적으로 사용하지 못한다는 단점을 가집니다.busy waiting
은 "Multiprogramming System"에서 다른 프로세스가 CPU를 할당받으면 생산적으로 사용할 수 있는 것을 "Busy Waiting"에서 의미없이 CPU 주기를 낭비합니다.
- 단일 프로세스 시스템 환경이라면, 가능합니다.
- 장점으로는 구현이 단순하며 & 인터럽트가 발생하지 않으면 코드가 실행 중에 다른 쓰레드가 중간에 끼어들지 않는다는 것을 보장할 수 있습니다.
- 단점으로는 단일 프로세스 시스템에서만 가능하고, 멀티 프로세스 시스템에서는 불가하다는 점입니다.
- 이유는 여러 쓰레드가 여러 CPU에서 실행 중이라면 각 쓰레드가 동일한 임계 영역을 진입하려고 시도할 수 있습니다. 그러나 이때에 특정 프로세서에서의 인터럽트 비활성화는 다른 프로세서에서 실행 중인 프로그램에는 전혀 영향을 주지 않기에 멀티 프로세스 시스템에서는 단순 interrupt로 상호배제를 예방할 수 없습니다.
-
데이터 정합성 문제가 발생합니다. 예시로 계좌 출입금 문제가 있습니다.
race condition
입니다.- race condition 은 2개 이상의 프로세스가 공유 자원을 병행적으로 읽거나 쓰는 상황인 경쟁 조건을 의미합니다.
- 여러 프로세서가 동시에 접근하면 시간차로 예상치 못한 결과를 만들 수 있는 것과 같이 실행순서에 따라 결과값이 달라지는 현상에서 발생합니다.
- SW 솔루션 (피터슨 알고리즘), HW 솔루션 (test&set , compare&swap 명령어), OS 지원 SW 솔루션 (Spin-Lock, Mutex, Semaphore) 이 있습니다.
-
PostgreSQL을 사용했는데, PostgreSQL은 동시성 이슈를 방지해주는 역할을 가지고 있어 경험은 없습니다.
-
동시성 문제
- 동일한 자원에 대해 여러 스레드가 동시에 접근 하면서 발생하는 문제입니다.
-
데이터 정합성 문제 :
- 데이터가 서로 모순 없이 일관되게 일치하는 상태입니다.
-
(+) JAVA, Spring 에서 동시성 문제/정합성 문제가 일어나면 해결하는 방식으로는, - LOCK (비관적 락, 낙관적 락)을 걸어줍니다. - synchronized 어노테이션을 통해 LOCK을 걸어줍니다.
-
트랜잭션
- 데이터베이스의 상태를 변화시키기 해서 수행하는 작업의 단위입니다.
-
트랜잭션의 특징 (ACID)
- Atomic (원자성)
- 트랜잭션이 데이터베이스에 모두 반영되던가, 아니면 전혀 반영되지 않아야 한다는 것입니다.
- Consistency (일관성)
- 트랜잭션의 작업 처리 결과가 항상 일관성이 있어야 한다는 것입니다.
- Isolation (독립성)
- 어떤 트랜잭션이든, 다른 트랜잭션의 연산에 끼어들 수 없습니다.
- Duration (지속성)
- 트랜잭션이 성공적으로 완료됬을 경우, 결과는 영구적으로 반영되어야 한다는 것입니다.
- Atomic (원자성)
-
트랜잭션의 Commit, Rollback
-
COMMIT
- Commit이란 하나의 트랜잭션이 성공적으로 끝났고, 데이터베이스가 일관성있는 상태에 있을 때, 하나의 트랜잭션이 끝났다라는 것을 가리킵니다.
-
ROLLBACK
- 하나의 트랜잭션 처리가 비정상적으로 종료되어 트랜잭션의 원자성이 깨진경우, 트랜잭션을 처음부터 다시 시작하거나, 트랜잭션의 부분적으로만 연산된 결과를 다시 취소시킵니다.
-
-
트랜잭션 격리 수준(isolation level)에 따른 데이터 정합성 문제
-
데이터 정합성이란?
- 데이터가 서로 모순 없이 일관되게 일치하는 상태입니다.
-
트랜잭션 격리 수준이란?
- 동시에 여러 트랜잭션이 처리될 때, 트랜잭션끼리 얼마나 서로 고립되어 있는지를 나타내는 것입니다.
- (4)를 제외한 (1),(2),(3) 레벨 수준의 낮은 격리 수준에서는 아래와 같은 데이터 정합성 문제가 일어날 수 있습니다.
(1) Read Uncommitted -
Dirty Read
어떤 트랜잭션에서 아직 실행이 끝나지 않은 다른 트랜잭션에 의한 변경사항을 보게되는 경우를 가리킵니다. 커밋되지 않은 수정중인 데이터를 다른 트랜잭션에서 읽을 수 있도록 허용할 때 발생하는 현상입니다.(2) Read Committed -
Non-Repeatable Read
한 트랜잭션에서 같은 쿼리를 두 번 수행할 때 그 사이에 다른 트랜잭션 값을 수정 또는 삭제하면서 두 쿼리의 결과가 상이하게 나타나는 일관성이 깨진 현상을 가리킵니다. 한 트랜잭션에서 똑같은 SELECT를 수행했을 때 항상 같은 결과를 반환해야 한다는 Repeatable Read 정합성에 어긋나는 현상입니다.(3) Repeatable Read -
Phantom Read
- 한 트랜잭션 안에서 일정 범위의 레코드를 두 번 이상 읽었을 때, 첫번째 쿼리에서 없던 레코드가 두번째 쿼리에서 나타나는 현상입니다. - 트랜잭션 도중 새로운 레코드 삽입을 허용하기 때문에 나타납니다.(4) Serializable (정합성 문제 발발 X) - 트랜잭션이 완료될 때까지 SELECT 문장이 사용되는 모든 데이터에 Shared Lock이 걸리는 계층입니다. - 가장 엄격한 격리 수준으로 완벽한 읽기 일관성 모드를 제공합니다. - 다른 사용자는 트랜잭션 영역에 해당되는 데이터에 대한 수정 및 입력이 불가합니다.
-
-
-
두 개 이상의 프로세스 / 스레드가 하나의 임계구역에 들어가서 객체에 동시에 접근하더라도 결과의 정합성을 보장하는 것이 가능한 것입니다.
- 동기화 처리를 적절히 해주면 됩니다.
- JAVA 에서 주로 사용하는 동기화 기법은 아래와 같습니다.
- (1) java.util.concurrent 제공해주는 동시성 보장 라이브러리 사용합니다.
- (2) Singleton 패턴을 도입합니다.
- (3) @synchronized 를 통해 동시성 관리를 해줍니다.
- 재진입성이라는 의미로, 어떤 함수가
Reentrant
하다는 것은, 여러 스레드가 동시에 접근해도 언제나 같은 실행 결과를 보장한다는 의미를 가집니다. - 이를 만족하기 위해서 해당 서브루틴에서는 공유자원을 사용하지 않으면 됩니다.
- 즉, 정적(전역) 변수를 사용하거나 반환하면 안 되고 호출 시 제공된 매개변수만으로 동작해야 합니다.
- 멀티태스킹 환경에서는 재진입 함수를 사용해야 합니다.
- 재진입성을 갖추지 못한 함수는 멀티 쓰레드에서 사용할 수 없습니다.
- 재진입성은 주로 전역번수를 안쓰고 로컬 변수를 사용함으로써 실현 가능합니다.
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=dong5053&logNo=220717508763 https://mommoo.tistory.com/62 https://dar0m.tistory.com/225 https://luv-n-interest.tistory.com/465 https://doitnow-man.tistory.com/110 https://velog.io/@hahahaa8642/OS-Spinlock-Semaphore-Mutex-Monitor https://cs-ssupport.tistory.com/m/428