Java Thread와 동시성에 관해

jj J·2022년 12월 3일
1

JAVA

목록 보기
10/15

이번엔 멘토링 수업에서 받았던 Thread 관련 질문 중 명확히 설명하지 못한 키워드들을 한데 모아서, 다시 한번 개념 정리를 해보려 한다. 키워드들은 아래와 같다.

  • race condition
  • spinlock
  • semaphore
  • mutex
  • deadlock
  • monitor

먼저 race condition부터 살펴보자.
race condition은 Multi Thread 환경에서 하나의 자원에 대해 발생할 수 있는 접근 순서가 중요한 의미를 가지게 되는 상황이다.
더 자세하게는 Critical Section, Multi Thread 환경에서 다수의 Thread들이 동시에 실행할 때, 서로 다른 결과가 나오는 동시 접근 문제가 발생할 수 있는 영역에서 발생할 수 있는 상황이다.

Multi Thread로 하나의 자원에 접근하는 것 자체가 문제는 아니다. 문제는 접근 이외에 Write를 수행할 때 발생한다. 다수의 Thread 중 어떤 Thread가 먼저 실행되고, 끝나는지 알 수 있는 방법은 없고 따라서, 여러 Thread 간 실행이 섞여 의도하지 않은 이상한 결과가 나오게 된다.

문제 예방

Critical Section의 원자성을 보장해야한다. 한 Thread가 실행하고 끝날때까지 다른 Thread에서 변경을 못하게 해야 한다.

이러한 방안으로는 synchronized 및 lock 사용, java.util.concurrent.atomic.AtomicInteger 같은 동시성을 보장하는 클래스 사용 등이 있다.

Synchronized의 효율적인 사용

Synchronized는 동시성을 보장해주는 수단이지만 해당 블럭 전체가 병렬 처리가 불가능해져 무분별한 사용은 성능 저하를 야기할 수 있다.
Synchronized 적용 범위를 잘게 나누어 분리하면 Thread들이 나눠진 부분을 따로따로 실행할 수 있고, Race Condition을 줄일 수 있게 되어 처리 성능을 향상시킬 수 있을 것이다.

DeadLock

Critical Section에 대한 여러 작업이 서로 끝나기를 기다리는 상태. 교착 상태라고도 한다.

DeadLock의 발생 조건은 4가지가 있고, 이 중 하나라도 회피하면 DeadLock이 해결된다.
1. 상호 배제 : 하나의 자원에 대해서는 하나의 Thread만 사용할 수 있다. 다른 Thread가 사용하려면 lock이 반환될 때까지 기다려야 한다.
2. 점유 대기 : 다른 프로세스가 점유 중인 자원을 대기하는 Thread가 존재해야 한다.
3. 비선점 : 이미 할당된 자원을 강제로 뺐을 수 없다.
4. 순환 대기 : 대기열이 순환 형태로 자원을 대기하고 있어야 한다.

DeadLock 해결법

위 4가지 중 하나라도 발생하지 않도록 하면 해결되는데, 예를 들어 해당 자원에 synchronized를 풀어 여러 Thread가 사용할 수 있도록 하거나, 작업에 대한 Timeout을 걸어 일정 시간이 지나면 무조건 lock을 반환하도록 하거나, 우선 순위를 두어 높은 우선순위의 Thread가 우선적으로 자원을 선점하도록 하면된다.
하지만 이러한 방법들은 시스템의 처리 성능이나 효율성을 떨어트릴 수 있어서, 조금 덜 제한적인 방법으로 단점을 해결한 방법을 살펴 볼 것이다.

DeadLock 회피(Avoidance)

시스템의 프로세스들이 요청하는 모든 자원을, DeadLock을 발생시키지 않으면서도 차례로 모두에게 할당해 줄 수 있다면 Safe State에 있다고 말한다.
그리고, 이처럼 특정 순서로 자원을 할당, 실행 등의 작업을 할때 DeadLock이 발생하지 않는 순서를 찾을 수 있다면 이를 Safe Sequence라고 한다.

자원을 할당한 후에도 항상 Safe State에 있을 수 있도록 할당을 허용하자는 것이 가장 큰 특징인데, 이러한 특징을 가진 대표적인 알고리즘이 은행원 알고리즘이다.

은행원 알고리즘(Banker's Algorithm)

어떤 자원의 할당을 허용하는 지에 관한 여부를 결정하기 전에, 미리 결정된 모든 자원들의 최대 가능한 할당량을 가지고 시뮬레이션해서 Safe State에 들 수 있는지 여부를 검사. 즉, 대기중인 다른 프로세스들의 활동에 대한 DeadLock 가능성을 미리 확인하는 것이다.

장점

미리 자원 최대 할당량을 파악해 DeadLock을 회피할 수 있음

단점

미리 자원 최대 할당량을 파악해야하고, 할당 가능한 자원 수가 일정해야 하는 등의 제약조건이 많고, 이에 따른 자원 이용도가 하락할 수 있음

DeadLock Detection 및 Recovery

DeadLock을 예방, 회피하지 않아 DeadLock이 발생하면, 여기서 회복하기 위해 Detection, Recovery 알고리즘을 사용한다.

Detection

Allocation, Request, Available 등으로 시스템에 DeadLock이 발생했는지 여부를 탐색. 현재 시스템 자원 할당 상태를 가지고 탐색한다.

Recovery

DeadLock이 Detection됬다면, 순환 대기에서 벗어나 DeadLock으로부터 회복하기 위한 방법을 사용
1. 프로세스를 중단시키기

  • DeadLock에 빠진 모든 프로세스를 중단시키는 방법 : 작업중이던 프로세스들도 모두 중단되어 부분 결과가 소실될 수 있음
  • 프로세스를 하나씩 중단, Detection, Recovery 반복하는 방법 : 매번 Detecion 실행해야 하므로 오버헤드 발생 부담
  1. 자원 선점하기
  • 할당된 자원을 선점해서, DeadLock이 해결될 떄까지 해당 자원을 다른 프로세스에 할당해주는 방법

Spinlock

다른 Thread가 Critical Section에 대한 lock을 점유하고 있다면, 반환할때 까지 계속 loop를 돌며 확인 및 대기하는 것

장점

Thread의 상태를 변경하지 않고 CPU를 계속 점유하기 때문에, Context Switching Cost가 발생하지 않음
Critical Section내 작업이 간단해 빨리 끝난다면, Context Switching Cost를 최소화할 수 있음

단점

Critical Section내 작업이 복잡해 오래 걸린다면, CPU가 다른 Thread 작업을 처리할 수 없도록 해두었기 때문에 busy wating 상태에 빠져, 비효율적인 처리 방법이 됨

Mutex

lock이 반환될 때까지 CPU를 점유하며 계속 대기하는게 아니라, 반환될 때 깨워달라 요청하고 대기 상태로 전환함

장점

Context Swithing을 하면서 lock이 반환될 때까지 최대한 효율적으로 다수의 Thread로 처리할 수 있다.

단점

멀티 코어 환경에 한해, Context Switching Cost가 있기 때문에, Critical Section 처리 비용이 Context switching 비용 보다 적다면 오히려 Spinlock 방식이 이점이 있을 수 있다.

Semaphore

다수의 프로세스가 Critical Section에 접근해 동기화하는 방법
Wait와 Signal 연산을 사용해, 모든 자원이 사용중이라면 Wait, 작업이 끝나면 Signal을 보내 대기중인 다른 프로세스가 자원을 사용하도록 한다.

profile
매일 발전

0개의 댓글