-
동시성 프로그래밍(5) Concurrency Problems(동시성과 관련된 문제들)iOS 2022. 2. 17. 16:06
안녕하세요.
오늘은 동시성 프로그래밍을 하면서 발생할 수 있는 문제들에 대해서 알아보겠습니다.
Thread-Safety
https://en.wikipedia.org/wiki/Thread_safety
Thread-Safety는 여러 스레드가 동시에 사용되어도 문제가 없음을 의미합니다.
여러 스레드에서 데이터(객체나 변수)를 사용하여도 개발자가 의도한 대로 결과가 나오는 것이죠.
그렇다면 Thread-safe하지 않은 경우도 존재한다는 것이겠죠?
오늘은 그중 아래의 3가지 경우를 살펴보겠습니다.
1. Race Condition(경쟁 상태)
2. Deadlocks(교착 상태)
3. Priority Inversion(우선순위의 뒤바뀜)
1. Race Condition(경쟁 상태)
https://en.wikipedia.org/wiki/Race_condition#In_software
경쟁 상태는 공용 자원에 대해서 여러 개의 스레드에서 접근할 때 그 순서에 따라서 결과가 항상 같지 않고 달라지는 상황을 의미합니다.
for _ in 0..<10 { var a = 0 DispatchQueue.global().async { a = 100 print("want 100: \(a)") } DispatchQueue.global().async { a = 200 print("want 200: \(a)") } }
위와 같은 코드가 경쟁 상태가 발생하는 경우입니다.
위의 코드는 실행시켰을 경우
매번 결과가 다르게 나오겠지만 위와 같이 100을 예상했는데 200이 출력되는 경우가 존재합니다.
그 이유를 그림을 통해서 알아보겠습니다.
각각의 작업은
a의 값을 수정하는 부분과 a를 출력하는 부분으로 나눌 수가 있습니다.
이럴 경우 Thread 3의 a의 값을 200으로 변경하는 작업이 Thread 2의 a를 출력하는 함수보다 먼저 실행될 수가 있는 것입니다.
이러한 상황은 개발자가 결과를 예측하지 못하게 하고 문제를 발생시킬 가능성이 있습니다.
2. Deadlock(교착 상태)
https://en.wikipedia.org/wiki/Deadlock
교착 상태는 두 개 이상의 스레드가 서로의 작업이 끝날 때까지 무한정 기다리는 상태를 의미합니다.
좁은 길에서 사람을 마주쳤을 때 서로 비켜주기만을 기다리고 계속 마주 보고 있는 상황이라고 생각하시면 이해가 될 것 같습니다.
그림으로 살펴보겠습니다.
A와 B라는 자원이 존재한다고 생각해봅시다.
두 자원은 경쟁 상태가 발생하면 안되는 자원입니다.
따라서 두 자원의 각각 접근할 때는 하나의 작업만이 접근할 수 있도록 잠금장치와 같은 조치를 취해야 합니다.
하지만 두 개의 자원을 모두 사용해야 하는 두 개의 작업 Task 1과 Task2가 존재합니다.
Task 1은 A의 먼저 사용하기 때문에 A를 잠급니다.
Task2는 B를 잠급니다.
Task 1과 Task2는 각각 B와 A에 접근한 후에야 A와 B의 잠금을 해제할 수 있습니다.
이럴 경우 서로가 서로의 자원이 해제되기만을 무한정 기다리는 상황이 발생합니다.교착 상태가 발생하기 위해서는 4가지 조건이 모두 필요합니다.
1. 상호 배제(Mutual Exclusion)
하나의 자원에는 하나의 작업만이 접근할 수 있다는 조건입니다.
A 또는 B에 여러 개의 작업이 접근할 수 있다면 다른 작업이 기다릴 필요가 없겠죠?
2. 점유 대기(Hold and Wait)
하나의 작업이 최소한 한 개의 자원을 점유하고 있고 다른 자원을 대기하고 있어야 합니다.
Task 1 또는 Task2가 자원을 모두 점유하고 있거나 아무런 자원도 대기하지 않으면 교착상태는 발생하지 않습니다.
3. 비선점(No Preemption)
작업은 다른 작업이 점유 중인 자원을 강제로 뺏어올 수 있습니다.
Task 1이 점유 중인 A를 Task2가 뺏어올 수 있다면 교착상태는 발생하지 않습니다.
4. 순환대기(Circular Wait)
작업의 요청이 순환적으로 이루어져 있어야 합니다.
작업의 요청을 타고 타고 가다 보면 결국 자기 자신에게 요청하게 되는 상황을 말합니다.
Task 1, Task 2, Task 3가 존재할 때, Task 1은 Task 2에게, Task 2는 Task 3에게, Task 3는 Task 1에게 요청하게 되어 무한 대기 상황에 빠집니다.
iOS 프로그래밍을 할 때 교착상황을 마주칠 수 있는 대표적인 경우는
DispatchQueue를 공부하면서 봤던 현재의 Queue에서 현재의 Queue로 동기적으로 작업을 보내는 경우입니다.
현재 스레드를 Block 하는 동시에 현재의 스레드에게 요청하기 때문에 이러지도 저러지도 못하는 상태에 빠지게 되는 것이죠.
또한 여러 개의 Semaphore를 사용할 때 순서를 잘못 설계하는 경우에도 교착상태에 빠질 수 있겠죠?
데드락의 개념을 이해하고 이러한 상황이 발생하지 않도록 프로그래밍을 하는 것이 중요합니다.
3. Priority Inversion(우선순위의 뒤바뀜)
DispatchQueue의 QoS를 공부하면서
시스템이 우선순위가 높은 작업에 더 많은 자원(스레드, 배터리 등)을 소모한다는 것을 알았습니다.
따라서 우선순위가 높은 작업이 낮은 작업에 비해 먼저 실행됩니다.
하지만 공유자원에 대해 배타적 접근만이 가능하도록 할 시 우선순위대로 실행되지 않는 경우가 존재합니다.
낮은 우선순위의 작업이 높은 우선순위의 작업보다 먼저 실행되는 경우를 우선순위의 뒤바뀜 현상이라 말합니다.
그림을 통해 알아보겠습니다.
우선순위가 다른 3개의 Queue에 Task 1, Task 2, Task 3가 각각 존재합니다.
그중에서 Task 1과 Task 3는 공유 자원 A에 접근해야 합니다.
Task 1이 실행이 되면 메모리 A에 다른 작업이 접근하지 못하도록 잠급니다.
그 후 Task 2가 실행되었으므로 시스템은 우선순위가 더 높은 Task 2에 더 많은 자원을 할당해 우선적으로 실행시킵니다.
Task 1은 일시적으로 중단되게 됩니다.
(여러 개의 스레드가 물리적으로 존재해 작업을 병렬적으로 실행하는 Parrallel Programming이 아닌 하나의 스레드에서 작업을 번갈아가면서 실행해 동시에 실행되는 것처럼 보이게 하는 Concurrency Programming의 경우입니다.)
Task 3가 실행되게 되면 우선순위가 가장 높기 때문에 Task 2도 Task 3의 작업이 완료될 때까지 멈추게 됩니다.
그러나 Task 3는 공유자원 A에 접근을 해야 하는데 잠겨 있기 때문에 접근을 할 수 없게 되죠.
시스템은 Task 3의 작업을 멈춥니다.
다음 우선순위의 작업이었던 Task 2를 실행시키고
공유 자원 A를 점유하고 있던 Task 1을 완료하고 나서 Task 3을 완료합니다.
작업이 Task 2 -> Task 1 -> Task 3 순으로 완료되는 것을 볼 수 있습니다.
Task 3의 우선순위가 가장 높았음에도 불구하고 가장 나중에 완료되는 것을 확인할 수 있습니다.
위와 같이 낮은 우선순위의 작업이 높은 우선순위가 필요로 하는 자원을 잠그고 있을 경우 우선순위 뒤바뀜 현상이 발생할 수 있습니다.
SerialQueue에서 낮은 우선순위의 작업이 높은 우선 순위의 자원보다 먼저 넣어지는 경우도 해당되겠죠?
사실 이러한 문제는 1차적으로는 GCD가 알아서 해결합니다.
그림에서와 같은 경우는 사실 Task 1 -> Task 3 -> Task 2의 순서로 실행됩니다.
GCD는 높은 우선순위가 필요로 하는 자원을 잠그고 있는 작업의 우선순위를 높입니다.
Task 3가 요구하는 공유 자원 A를 점유하고 있는 Task 1의 우선순위를 높여버려 먼저 실행되도록 합니다.
또한 일반적으로 공유자원에 접근해야 하는 작업의 경우에는
동일한 QoS를 사용하는 것이 우선순위 뒤바뀜 문제의 발생 가능성을 낮출 수 있습니다.
오늘은 동시성 프로그래밍을 하면서 발생할 수 있는 문제들에 대해서 알아보았습니다.
다음 시간에는 이러한 문제들을 해결하면서(특히, 경쟁 상태) Thread-Safe한 코드를 작성하는 방법을 알아보겠습니다.
Ref.
https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation/dashboard
'iOS' 카테고리의 다른 글
동시성 프로그래밍(7) Operation (0) 2022.03.15 동시성 프로그래밍(6) Race Condition(경쟁 상태) 해결 방법 (1) 2022.03.09 동시성 프로그래밍(4) DispatchGroup, DispatchWorkItem, DispatchSemaphore (0) 2022.02.14 동시성 프로그래밍(3) DispatchQueue (0) 2022.02.10 동시성 프로그래밍(2) Serial(직렬) VS Concurrency(동시) (0) 2022.02.04