[TIL] 메인 스레드에서 main.sync는 왜 안될까?

숑이·2023년 7월 30일
1

iOS

목록 보기
16/26
post-thumbnail

이전에 GCD를 사용해서 동시성 프로그래밍을 하는 방법과 동기(Synchronous)와 비동기(Asynchronous) 그리고 직렬(Serial)과 동시(Concurrent)에 대해서 공부했었습니다.

GCD가 뭔지 모른다면 이 글을 참고해주세요!

그런데 한가지 주의해야할 것이 있습니다.

메인 스레드에서 Main DispatchQueue에 동기적(Synchronous)으로 작업을 보내면 안된다.

왜 안되는지 설명하실 수 있나요?
굳이 알아야하냐고요?

메인 스레드에서 main.sync를 쓸 일이 없긴 하지만, 왜 안되는지 궁금하잖아요.. ㅎㅎ

그럼 먼저 메인 스레드에서 Main DispatchQueue에 비동기적으로 작업을 보내는 코드를 작성해볼까요?

main.async

DispatchQueue.main.async {
// task A
    print("start")
    sleep(3)
}
DispatchQueue.main.async {
// task B
    print("end")
}

이 코드의 실행 결과는 다음과 같습니다.

start
// 3초 후...
end

예상대로 실행결과가 나왔나요?

비동기적으로 작업을 큐로 보내버렸죠? 근데 비동기는 작업의 완료를 대기하지 않고 다음 작업을 바로 실행한다고 했었습니다.
그런데 왜 task A의 작업이 끝날때까지 task B의 작업이 실행되지 않았을까요?

메인 스레드가 어떤 작업을 담당하고, 몇 개 있는지, 그리고 직렬과 동시에 대해 이해하고 있다면 답변할 수 있을 것 입니다.

메인 스레드는 UI를 담당하는 1개밖에 없는 스레드죠? 스레드가 1개밖에 없으니 당연히 직렬(Serial)일 수 밖에 없습니다.

이해를 돕기 위해서 그림으로 코드 동작을 확인해보겠습니다.

비동기적으로 실행했기 때문에 Task A를 Main DispatchQueue에 보내고, Task A의 완료를 대기하지 않은 상태로 바로 다음 Task B를 큐에 보냅니다.

큐는 들어온 순서대로 나가는 FIFO(First In First Out)형태의 자료구조입니다.
Task A가 먼저 큐에 들어왔기 때문에 먼저 스레드에 배치되겠죠?

GCD가 적절한 스레드에 Task A를 배치해줄거에요.

우리는 메인 스레드에서 작업을 하기 위해서 Main DispatchQueue에 작업을 보냈죠?
근데 메인 스레드는 1개밖에 없습니다.

그렇기 때문에 메인 스레드에 직렬로 작업이 배치됩니다.

결과적으로 Task B도 메인 스레드에 배치될거에요.
직렬로 1개의 스레드에 배치됐기 때문에 Task A와 Task B는 순차적으로 실행됩니다.

print("start")
sleep(3)
print("end")

main.async로 메인 스레드에 비동기적으로 작업을 보냈지만, 결국에는 위 코드와 동일한 결과를 출력합니다.

당연한 얘기를 구구절절 한 것 같지만,,
Async와 Serial 각각의 의미에 대해 다시 상기시키는데에는 도움이 되는 것 같습니다.

그렇다면, main.sync를 했을 때에도 main.async와 동일한 결과가 나올까요?

main.sync

이 코드는 에러를 뿜뿜!합니다

Main Thread에서 Main DispatchQueue에 동기(Sync)로 Task A와 Task B를 보내고 있습니다.
그렇다면, 왜 오류가 발생할까요?

Main Thread는 Main DispatchQueue에 동기적으로 작업을 보냈습니다.
즉, Task A가 완료될 때까지 Task B를 수행하지 못하도록 대기합니다.

GCD는 Queue에 있는 Task A를 꺼내서 메인 스레드에 배치하려고 합니다.

그런데, Main Thread는 Task A의 완료를 대기하고 있죠?
GCD는 이러지도 저러지도 못하고, Task A를 메인스레드에 할당하지 못합니다.
Task A가 스레드를 할당받지 못해 작업이 지연되면, 메인 스레드는 작업이 계속 지연되겠죠?

이런 상황을 Dead Lock(교착 상태)라고 합니다.
Dead Lock이 발생하면, 결국 Task A는 실행조차 못하고, 영원히 서로를 기다리는 상황에 빠지게 됩니다.

DispatchQueue.main.sync {
    // task A
    print("start")
    sleep(3)
}

그렇기 때문에 이 코드는 교착 상태에 빠질 수 있는 코드이고, 에러가 발생합니다.

하지만, 이는 메인 스레드에서 이 코드를 실행하는 경우에 발생하는 문제이고, 백그라운드 스레드에서 실행한다면 괜찮습니다.

DispatchQueue.global().async {
    DispatchQueue.main.sync {
        // task A
        print("start")
        sleep(3)
    }
    DispatchQueue.main.sync {
        // task B
        print("end")
    }
}

이렇게요! :)
어렵지 않게 이해할 수 있죠?

우리는 그저

메인 스레드에서 main.sync를 실행하면 Dead Lock(교착 상태)이 발생하기 때문에 에러가 발생한다.

라고 알고 있으면 됩니다.

끝!

profile
iOS앱 개발자가 될테야

0개의 댓글