티스토리 뷰

iOS

iOS ) 왜 main.sync를 하면 안될까

Zedd0202 2018. 4. 29. 13:03
반응형

안녕하세요 :) Zedd입니다.

오늘은 왜!!! 왜 main.sync를 하면 안되는지 자세히 공부해볼게요.

그렇다고 main.sync를 무조건 하면 안된다는것도 아닙니다.

요 부분은 밑에서 다시 볼게요.

 

main.sync를 하게 되면

이런 에러를 뿜게 되는데요, 이게 왜 나는건지!!!! 알아봅시다.

main.sync가 왜 에러가 난다!!를 말하기 전에, 알아야 할 것이 있습니다.

 

대부분 GCD 글을 보게 되면, UI업데이트는 반드시 main에서 해야한다고 말하는것을 볼 수 있어요!

그 이유부터 봅시다. 이유는 들어보면 간단한데요,

Cocoa Touch 앱에서 UIApplication의 인스턴스가 main thread에 붙기(attach) 때문.
UIApplication은 앱을 시작할 때 인스턴스화 되는 앱의 첫번째 부분인데,
얘는 앱의 run loop를 포함하여 main event loop를 설정하고 event처리를 시작합니다.
앱의 main event loop는 touch, gesture등의 모든 UI event를 수신합니다. 

앱의 UI event는 일반적으로 UIApplication -> UIWindow -> UIViewController -> UIView -> subviews (UIButton 등)와 같이 chain으로 연결되는데, 이 responder chain을 따라 UIResponder로 전달됩니다. 

Responder는 버튼 누르기, 탭, pinch, 확대/축소, 스와이프 등의 이벤트를 UI 변경사항으로 처리합니다. 따라서 이러한 일련의 event chain이 UIKit의 main thread에서 작동하는 이유입니다. 

따지고 보면, label이나 View는 UIApplication의 자손? 자식이므로, 모든 이런 UI작업은 main thread의 “일부”라고 할 수 있어요. 그래서 모든 이벤트는!!! main thread의 일부가 되며, main thread에서 처리해야합니다. 

 

UIKit문서에 가보면,

대부분의 경우, UIKit 클래스는 앱의 main thread에서만 사용해야 합니다. 이는 UIResponder에서 파생되거나 앱의 사용자 인터페이스를 조작하는 것과 관련하여 특히 그렇습니다.

 

구구절절 설명했지만, UI(user interface)와 관련된 모든 event가 main thread에 붙기(attach)때문에, 반드시 main에서 해야한다는 것입니다.<Dispatch Queue>글에서도 설명했지만, dispatch queue에서 제공해주는 main은 

Main dispatch queue : main dispatch queue는 앱의 main 쓰레드에서 task를 실행하는, 전역적으로 사용 가능한 serial queue입니다. 이 큐는 앱의 run loop와 함께 작동하여 큐에 있는 task의 실행을 run loop에 연결된 다른 이벤트 소스의 실행과 얽힙니다. 앱의 main thread에서 실행되므로, main queue는 종종 앱의 주요 동기화 지점으로 사용됩니다. main dispatch queue를 만들 필요는 없지만, 앱이 적절하게 배수(drains)되도록 해야합니다. 

그렇습니다. 그렇기 때문에 우리가 UI와 관련된 업데이트를 치고싶으면 바로 이 main에서 해야하는이유죠. 

 

그러면, 왜!!!! 왜 main.sync를 하면 안되는걸까요. 

 

일단 main queue는 Serial queue입니다. 한 task가 끝나야 (큐에 있는) 다음 task를 실행하는것이죠.

그리고 sync는 이 큐에 있는 작업이 끝날 때 까지 그 코드에 머물러 있죠. 즉, 큐를 block하고 우리가 큐에 넣은 작업들이 완료될때까지 wait상태가 되는 것이죠. 

 

만약 main queue에서 sync를 호출하게 되면, 일단 큐를 block하고 우리가 넣은 task가 끝날때까지 기다리겠네요. 하지만!!!!!!!!!!!!!!!!!!!!! 큐는 이미 block상태가 되었기 때문에 task는 시작조차 할 수 없습니다. 이러한 상태를 deadlock이라고 하는데요, 무한정 기다리는 상태가 되어 앱이 죽게되고맙니다. 

 

그럼 이러한 궁금증이 들 수 있겠죠. 

아 ㅇㅇㅋㅇㅋ;; UI를 main에서 업데이트 해야한다는건 알겠음ㅎㅎ;근데 왜 하필 main.sync가 안되냐고 ㅎㅎ;

let queue = DispatchQueue(label: "zedd")

queue.sync {

    for index in 1...5 {
        print("hello, zedd \(index)")
    }
}

for i in 100...105 {
    print(i)
}

내가 임의로 Serial queue만들면 잘 되는 부분;;

네 그렇습니다. 잘 되죠. 

왜 하필 main.sync만 안되냐??????????????????? 

그것은 바로....확실하진 않지만...main thread가 thead-safe하지 않기 때문입니다.

 objc.io에서 

 


UIKit을 thread부터 보호하지 않는 것은 Apple 측의 의식적인 디자인 결정입니다.(일부러 그렇게 만들었다는 뜻) thread로부터 안전하게 만들면 성능면에서별로 도움이되지 않습니다. 실제로 많은 일이 느려질 것입니다. 

그리고 UIKit이 main thread에 묶여 있다는 사실은, 

concurrent 프로그램을 작성하고 UIKit을 사용하는 것을 매우 쉽게 만듭니다. 

당신이해야 할 일은 항상 UIKit에 대한 호출이 항상 main thread에서 이루어 지도록하는 것입니다.

따라서 UIKit 객체가 main thread에서 액세스되어야한다는 사실은,

 성능에 유리하도록 한 apple의 디자인 결정입니다 


 

 

라고 해서...일단 진짜 그런가 했더니

 

<Thread Safety Summary>에서 이렇게 말합니다.

There is a misconception that the Foundation framework is thread-safe and the Application Kit framework is not. Unfortunately, this is a gross generalization and somewhat misleading. Each framework has areas that are thread-safe and areas that are not thread-safe. 

 
Foundation프레임워크는 쓰레드로부터 안전하고, Application Kit프레임워크(AppKit인데, Cocoa touch에서 UIKit과 동일합니다. UIKit으로 읽으셔도 무방)는 그렇지 않다는 오해가 있습니다. 불행히도, 이것은 총체적인 일반화이며, 오해의 소지가 있습니다. 각 프레임워크에는 쓰레드로부터 안전한 영역과 쓰레드로부터 안전하지 않은 영역이 있습니다. 
 
그리고 iOS 4.0(ㄷㄷ) release note에 보면
 

Drawing to a graphics context in UIKit is now thread-safe. Specifically:

 

  • The routines used to access and manipulate the graphics context can now correctly handle contexts residing on different threads.
  • String and image drawing is now thread-safe.
  • Using color and font objects in multiple threads is now safe to do.
 
 

UIKit에 있는 모든 것?이 thread로 부터 unsafe하지 않다! 라는 것을 말해주네요. 그러니까 모든 UIKit과 관련된 작업들은 thread-safe하지 않다! 라고 말하면 안되...겠죠?

 

아무튼...음...main thread가 thread-unsafe하니까 deadlock이 나는 것 같은데...확실하진 않아서 ㅎㅎ 엄청 찾아봐도 main thread는 unsafe한 thread란다^^;...딱 이렇게 말하는 문서가 안나오네요;;만약 틀렸다면 알려주세요!!!!!!!!!!!!!!!!!!꼭!!!!!!!!!!!

 

<Threading Programming Guide>에서 

Immutable object는 일반적으로 thread로 부터 안전하고, mutable object는 thread로 부터 안전하지 않다고 하는데 (당연한 말이겠죠? 변경이 불가능 하면 여러 thread에서 접근해도 상관없으니깐..) 그럼 main thread가 mutable인지... Immutable인지 모르겠네요. 찾아봐도 안나오고..main thread가 변경이 가능한가...??......ㅎㅎ.................어케 변경하지.... Immutable아닌가...음..잘 모르겠네요. 알려주시면 감사하겠..

 

+ 그렇다고 main.sync를 무조건 하면 안된다는것도 아닙니다.

 

main.sync를 백그라운드 스레드에서 특별한 순서에 맞게 UI가 업데이트 되어야 할 때 사용할 수 있습니다.

DispatchQueue.global().async {
    print("before textLabel update" )
    
    DispatchQueue.main.sync {
        print("Test1")
    }
    
    print("after textLabel update Test1")
    
    DispatchQueue.main.sync {
        print("Test2")
    }
    
    print("after textLabel update Test2" )
}

// before textLabel update
// Test1
// after textLabel update Test1
// Test2
// after textLabel update Test2



도움이 되었길 바랍니다 :)

 

 

반응형
TAG
댓글
  • 프로필사진 abc 안녕하세요. 해당 블로그를 보다가 의견을 공유하고 싶어서 글 남깁니다.

    zedd님께서 언급하신 부분 :
    (내가 임의로 Serial queue만들면 잘 되는 부분;;
    네 그렇습니다. 잘 되죠.
    왜 하필 main.sync만 안되냐??????????????????? )

    의 코드를 보면 main 큐에서 돌리신거 같은데 그 코드를
    let queue = DispatchQueue(label: "zedd")

    queue.sync { // 1
    for index in 1...5 {
    print("hello, zedd \(index)")
    }

    queue.sync { // 2
    print("!!")
    }

    print("##")
    }
    이렇게 바꿔보면 main.sync처럼 queue.sync에서 죽습니다. queue라는 직렬큐는 먼저 1번 블럭이 들어가 있고 이를 다 마치고 그 다음 작업(2)을 해야하는데 1번을 실행하는 도중 2번을 만났고.. 근데 2번 작업은 sync이다보니 1번 작업을 계속 끝낼수 없는 상태? 그래서 데드락으로 죽는..
    queue.sync을 queue.async로 변경하면 이미 들어간 1번작업이 다 끝나고 (##이 찍히고) 1번 작업 중간에 들어간 2번 작업 다음 작업이 되어 !!이 찍힙니다.
    그래서 main.sync만 안된다기 보다는 직렬큐안에 있는 그큐.sync 가 모두 죽는 것 같습니다.

    물론 뇌피셜이긴 하지만 암튼 많은 정보를 얻었습니다. 감사합니다.
    2020.03.18 15:52
  • 프로필사진 Favicon of https://zeddios.tistory.com BlogIcon Zedd0202 우와!! 감사드려요 저도 분석하고 abc님 댓글을 글에 추가해도 될까요? 2020.03.19 16:30 신고
  • 프로필사진 Favicon of https://badassdev.tistory.com BlogIcon 짬바_로그 저 꼬리에 꼬리를 무는 호기심들은 진짜 생기는 건가요 아니면 ㅋㅋㅋ 너무 멋진 블로그를 가진 사람으로서 책임감으로 생기는 건가요? ㅎㅎ 2021.01.09 10:41 신고
  • 프로필사진 Favicon of https://zepplin.tistory.com BlogIcon 천청 UIKit에서 Thread에 unsafe한 것들은 대부분 draw에 연관되는 접근입니다.
    예로 UILabel.text를 변경하는 접근은 새로운 text를 draw하게 할 것이고,
    이게 main 외의 Thread에서 이루어지게 되면 한 개의 Thread에서만 접근해야 하는 Graphic context에 영향을 주게 되는 것이지요. ( UIKit의 경우 이게 Main이기 때문 )

    반면 UIImage 같은 경우 Draw와 직접적인 연관이 없기 때문에 Worker Thread에서 생성 > Main으로 복사 후 사용과 같이 사용할 수도 있습니다.
    하지만 공식적으로 safe함을 지원하는 것이 아니기에 OS 버전에 따라 언제든지 unsafe해질 수 있음을 감수해야 하는 것이지요.
    2021.04.07 12:03 신고
  • 프로필사진 bing 안녕하세요 좋은 글 잘 보고 있습니다.
    main.sync는 무조건 사용하면 안된다는 의미로 해석될 것 같아 추가 의견 남깁니다.
    main.sync를 백그라운드 스레드에서 특별한 순서에 맞게 UI가 업데이트 되어야 할 때 사용할 수 있습니다.

    DispatchQueue.global().async {
    print("before textLabel update" )

    DispatchQueue.main.sync {
    self.textLabel.text = "Test1"
    print("update textlabel", self.textLabel.text)
    }

    print("after textLabel update Test1")

    DispatchQueue.main.sync {
    self.textLabel.text = "Test2"
    print("update textlabel", self.textLabel.text)
    }

    print("after textLabel update Test2" )
    }
    참고할만한 링크 첨부합니다.
    https://stackoverflow.com/a/42773392/5845281
    감사합니다
    2021.07.01 18:54
  • 프로필사진 흑우 감사해요 초고수 선생님 2021.11.08 14:05
  • 프로필사진 g https://developer.apple.com/documentation/dispatch/dispatchqueue/1452870-sync

    > Calling this function and targeting the current queue results in deadlock.
    2022.02.07 13:36
  • 프로필사진 swift 코린이 너무 궁금했던 내용인데 정말 감사합니다!! 2022.06.30 15:54
댓글쓰기 폼
반응형
Total
4,015,933
Today
425
Yesterday
2,463