본문 바로가기
ios

GCD에서 의도치 않은 교착상태를 피하는 법

by 고고 2021. 12. 22.
print("hi \(Thread.current)") // 1 

DispatchQueue.global().sync {
    print("hi \(Thread.current)") // 2
}

DispatchQueue.global().async {
    print("hi \(Thread.current)") // 3
}


// 1 - hi <_NSMainThread: 0x60000038c000>{number = 1, name = main}
// 2 - hi <_NSMainThread: 0x60000038c000>{number = 1, name = main}
// 3 - hi <NSThread: 0x600000389600>{number = 4, name = (null)}

1번 부분을 보면 메인 스레드에서 실행되고 있음을 알 수 있습니다.

원래 특별한 설정 없는 모든 코드는 메인스레드 위에서 돌아갑니다.

 

 

그럼 2번 부분은 왜 메인 스레드에서 처리될까요?

논리적으로는 메인 스레드를 block하고 다른 스레드에서 실행되어야 되는 거 아닌가요?

 

 

2번 부분은 실제로는 아래처럼 생겼습니다.

DispatchQueue.main.sync { // 바깥쪽
    DispatchQueue.global().sync { // 안쪽
        print("hi \(Thread.current)") // 사실은 메인스레드임 ㅎㅎ
    }
}

 

논리적으로는 바깥쪽 스레드에서는 안쪽 스레드가 2번 부분(task)을 처리하는 것을 기다려야 합니다.

하지만 실제로는 '어차피 block돼서 아무것도 못하긴 싫으니까 일을 내가 하자'는 마인드로 task를 안쪽 스레드에 할당하지 않고, 바깥쪽 스레드가 직접 처리하게 구현되어있습니다.

 

그래서 메인 스레드라고 뜨는 것입니다.

 

 

 

3번의 경우, 메인스레드가 비동기로 기다리는 것이니

메인스레드 : 다른 스레드에 task를 넘기고 나는 나한테 주어진 일이나 열심히 하자~

라서 다른 스레드로 뜨는 것입니다.

DispatchQueue.main.sync {
    DispatchQueue.global().async {
        print("hi \(Thread.current)") // 3
    }
}

 

 

 

 

그러면 우리는 교착상태를 실수로 아주 쉽게 만들 수 있습니다.

DispatchQueue(label: "test.serial").sync { // concurrentQueue.sync여도 같은 결과.
    print("hi \(Thread.current)") // 1
    DispatchQueue.main.sync {
        // 교착상태!!
    }
}

// hi <_NSMainThread: 0x6000020381c0>{number = 1, name = main}
// error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

위 코드는 사실 아래 코드가 되는 거라서 에러가 나는 겁니다.

DispatchQueue.main.sync {
    print("hi \(Thread.current)") // 1
    DispatchQueue.main.sync {
        // 교착상태!!
    }
}

같은 스레드 sync에서 sync를 호출하니 교착상태가 생기는 것은 당연합니다.

 

 

 

이 교착상태를 방지하려면 코드를 아래처럼 맨 처음에 async로 하게 해서 메인스레드에서 실행하지 않도록 해야합니다.

DispatchQueue(label: "test.serial").async {
    print("hi \(Thread.current)") // 1
    DispatchQueue.main.sync {
        print("hi \(Thread.current)") // 2
    }
}

// hi <NSThread: 0x600000995940>{number = 4, name = (null)}
// hi <_NSMainThread: 0x6000009801c0>{number = 1, name = main}

 

 

 

이 글에서 배우는 점

 

1. 특별한 설정 없는 모든 코드는 메인스레드 위에서 돌아간다.

2. 같은 스레드의 sync 속 sync를 조심하자.

'ios' 카테고리의 다른 글

How to show <private> in os_log  (0) 2022.05.10
iOS 라이브러리 배포하는 법 - CocoaPods, SPM, Carthage  (0) 2022.03.07
DispatchQueue 교착상태  (0) 2021.12.22
DispatchQueue 종류 세 가지  (0) 2021.12.21
동기/비동기, 직렬/동시  (0) 2021.12.21

댓글