Main Thread는 오직 한개 뿐이며, 나머지는 모두 Background Thread 입니다.
우리가 일반적으로 작성하는 대부분의 코드는 MainThread에서 실행됩니다. 이 사실을 잘 의식하지 못하는 이유는 우리가 작성한 코드가 Cocoa에서 실행되는데 이 Cocoa가 코드를 main thread에서 호출하기 때문입니다.
Main thread는 interface thread라고도 불립니다. 유저가 interface에 접근하면 이벤트는 main thread로 전달되고 우리가 작성한 코드는 이에 반응하게 됩니다. 이 말은 곧 인터페이스에 관련된 코드는 반드시 main thread에서 작성되어야 함을 의미합니다. 유저의 눈과 손이 닿는 부분은 모두 main thread에서 구현되어야 한다고 생각하면 좋을 것 같습니다.
혹시라도 실수로 인터페이스와 관련된 코드를 background에 구현하게 되면 Main Thread Checker가 보라색으로 경고를 표시해줍니다.
저는 개인적으로 background thread가 더 중요하다고 생각합니다. 그 이유는 이 부분을 잘 모르면 app에 치명적인 영향을 줄 수 있기때문인데요. 그 이유에 대해 알아보겠습니다.
iOS의 framework들은 background에서 구동됩니다. 몸체는 background에 있고 가끔씩 main thread에 손(delegate)을 뻗는다고 생각하면 좋을 것 같습니다. 이해를 돕기 위해 아래에서 예를 몇 가지 들어보겠습니다.
The Core Animationanimation frame의 작업은 background에서 실행이 됩니다. animation이 동작한다고 user interaction이 막힌다면 animation을 쓸 수 없겠죠? layer를 업데이트 시키는 animation 동작은 background thread에서 실행이 되고 우리는 delegate method와 completion handler를 이용하여 main thread에서 animation을 컨트롤 할 수가 있습니다.
Webview에서 content를 가져오는 작업 역시 background에서 수행됩니다. 마찬가지로 delegate method를 이용하여 main thread에서 컨트롤 할 수 있습니다.
음악을 재생하거나 영상을 가져오거나 content를 저장하는 작업들도 모두 background에서 수행됩니다. 그리고 보통 이렇게 background에서 작업되는 기능들은 delegate의 method나 completion handler를 통해 main thread에서 컨트롤 할 수 있습니다.
이 외에도 화면에 바로 나타나지 않는 작업들은 대부분 background thread에서 작업되는 경우가 많고 사실 그래야만 합니다. main thread에서 이렇게 시간이 걸리는 작업들을 수행하게 되면 아마 app은 항상 멈춰 있어야 하겠죠? 작업은 background thread에 맡기고 delegate method와 callback 함수로 main thread에서 호출하여 컨트롤하는 것이 일반적입니다.
위에서 살펴보았 듯이 일반적으로는 background의 존재를 생각하지 않고 delegate method와 completion handler를 이용하여 main thread에서 컨트롤만 해주면 됩니다. 하지만 명시적으로 background에 코드를 작성해야하는 경우가 있는데요. 첫 째는 code가 call back은 되는데 main thread에 없는 경우이고, 둘 째는 code 수행에 시간이 지나치게 오래걸리는 경우입니다. 한 가지씩 알아보겠습니다.
code가 call back은 되는데 main thread에 없는 경우
- 말이 좀 어렵게 느껴질 수 있습니다만, 쉽게 말해서 여러분이 작성한 코드가 여러분의 의도와 상관없이 background에서 실행되는 경우입니다.
- 예를 들어, CATiledLayer의 draw(_:in:)는 background thread에서 호출된다고 apple문서에 명시되어 있습니다. 여러분이 만약 draw(_in:)메서드를 사용해서 코드를 구현했다면 그 코드 내용은 background에서 실행됩니다. 이처럼 어떤 framework들은 document상으로 callback 함수가 main thread에서 실행되는 것을 보장하지 않는다고 되어있습니다. 따라서, 이런 경우에는 여러분이 background thread에서 작업이 될 것을 알고 코드를 작성하는 것이 중요합니다.
- AVFoundation framework의 경우에도 apple 문서에 completion function과 notification이 background thread에서 실행될 수 있다고 되어있습니다. 만약에 여러분이 completion function이나 notification를 사용해서 UI를 update해야한다던가 혹은 main thread에 있는 value를 가져다 써야할 경우에는 해당 부분을 명식적으로 main thread로 switch 해주어야 합니다. 자세한 방법은 뒤에서 다루도록 하겠습니다.
code 수행에 시간이 지나치게 오래걸리는 경우
- 말그대로 실행에 시간이 많이 걸리는 code를 main thread에 구현한다면 UI가 마비되는 상황이 발생할 수 있기때문에 이런 코드는 의도적으로 background thread에 구현해야합니다.
- 사용자가 버튼을 눌러서 이미지를 다운로드 한다고 생각해봅시다. 만약 이미지 다운로드 code를 main thread에서 실행한다면 이미지를 다운받는 동안 UI는 멈추게 됩니다.
- 어떤 경우라도 main thread가 block되게 해서는 안됩니다. 만약 너무 오랜 시간동안 block되면 app이 강제종료 될 수도 있습니다.
앞에서 thread의 기본적인 개념을 알아 봤는데요. 여기서는 개발자 입장에서 background thread가 얼마나 위험하고 난해한 대상인지 설명해보고자 합니다.
code는 한 줄 씩 차례대로 실행이 됩니다. 윗 줄에 있는 코드가 실행이 완료되어야 그 다음 줄의 코드가 실행이 되죠. 하지만 background thread가 개입하기 시작하면 이 원리가 깨집니다. 작업이 완료되는 순서를 가늠할 수 없게 됩니다. background thread에 작업이 많아질 수록 의도한 순서대로 코드가 실행되는 것에 어려움을 느끼게 될것입니다. 더군다나 자신이 작성한 코드가 background thread에서 실행되는지 조차 모른다면 문제는 더욱 심각해집니다.
main thread와 background thread는 병렬적(concurrent)으로 작업을 수행합니다. 이 말은 main thread에서 실행되고 있는 code 앞 줄과 뒷 줄 사이에 background thread에서 실행된 code가 언제 어떤 식으로 개입할지 알 수 없음을 의미합니다. 여러개의 background thread에서 작업이 이루어지고 있다면 한 번에 여러 개의 code가 개입할 수도 있습니다.
singlton으로 인스턴스화 된 class내부에 property가 있다고 가정해봅시다. 그리고 그 property에 접근 하는 thread가 많을 경우, 특정 thread에서 그 property로 작업을 하는 중인데 다른 thread에서 그 property에 접근하여 값을 바꿔버린다면 무슨 일이 벌어질까요? 생각만해도 아찔합니다. cuncurrent로 code가 실행되는 상황에서는 어느 순간 어떤 code가 의도치 않게 개입해올지 모른다는 위험이 항상 있습니다. 이를 염두에 두고 code를 작성해야 합니다.
위의 문제들을 방지하기 위해서, 하나의 code section에 두 개 이상의 thread가 접근하지 못하도록 lock을 거는 방법을 떠올릴 수 있지만, lock자체가 상당한 risk가 될 수도 있습니다. 잘못 세팅하면 어떤 thread도 접근할 수 없는 사용 불가 code section이 될 수도 있습니다.
또 다른 문제상황은 thread의 lifetime이 객체의 lifetime과 관련성을 갖지 않는 다는 점인데요. 이 말은 thread가 이미 deinit이 되어버린 객체에 접근을 시도하는 경우가 발생할 수도 있다는 것입니다. 운이 나쁘면 app에 crash가 발생할 수도 있고 deinit이 되어야 하는 객체를 deinit되지 못하게 만들 수도 있습니다.
thread를 고려하면서 code를 구현하는 것도 어렵지만 설상가상으로 test와 debug하는 것 역시 어렵습니다. 불확실성(어떨때는 되고 어떨때는 안되고 하는) 때문에 test에서는 발견되지 않았다가 유저가 app을 사용할 때 문제가 나타날 수도 있고 문제가 발생하더라도 문제가 발생한 원인을 찾기가 쉽지 않습니다.
앞에서 background thread가 어떻게 위험할 수 있는지 알아봤는데요. 이렇게 risk가 큰 부분에 대해서 apple이 아무런 조치도 취하지 않았을리 없습니다. 문제가 발생했을 때 도움을 줄 수 있는 4가지 tool이 있습니다.
Debug navigator: thread를 구분하여 대기중인 call이 enqueue되는 시점을 알려줍니다.
NSLog와 os_log: 우측 하단 console창에 호출된 thread를 식별할 수 있는 번호를 print해줍니다.
Time Profiler: 다른 thread의 활동을 기록해줍니다.
Thread Sanitizer: 발생할 수 있는 문제를 감지합니다.
다음 블로그 포스트에서는 thread를 관리하기 위한 3가지 방법으로 Manual Threading, Operation, GCD에 대해서 알아보겠습니다.
감사합니다!! 많은 도움이 되었습니다~!! 혹시 제 블로그에 해당 글 출처를 밝히고 정리한 내용을 업로드 해도 될까요?