티스토리 뷰

iOS 일반/Apple Guide

About Threaded Programming

찜토끼 2017. 6. 22. 13:36

Apple 에서 제공하는 프로그래밍 가이드 중 Threading Programming Guide의 About Threaded Programming 챕터를 공부하며 기록용으로 정리한 글입니다. 전문을 번역한 글이 아닌 점 참고바랍니다.



들어가며

오랫동안 컴퓨터의 최대 퍼포먼스는 싱글 마이크로프로세서의 속도에 따라 크게 좌우되었습니다. 그러나 멀티코어 디자인이 도입되면서 컴퓨터는 여러 테스크를 동시에 수행할 수 있는 기회를 제공받게 되었습니다. OS X는 시스템 관련 테스크를 수행할 때 이런 멀티코어를 활용하고 있으며, 당신의 어플리케이션 역시 쓰레드를 통해 이런 것들을 활용할 수 있습니다. 그럼 지금부터 쓰레드라는 것의 개념과 역할에 대해 알아보겠습니다.



What Are Threads?

쓰레드 (Thread) 는 어플리케이션 내에서 여러 개의 실행 경로 (multiple paths of execution) 를 구현하는 비교적 간단한 방법입니다. 시스템 레벨에서, 프로그램들은 나란히 (side by side) 실행이 됩니다. 시스템은 실행되고 있는 프로그램 각각의 요구에 따라 실행 시간 (execution time) 을 조금씩 나누어 줍니다. 그리고 각각의 프로그램 내부에서는 동시에 다른 테스크를 수행할 수 있도록 하나 이상의 쓰레드를 가질 수 있습니다. 이런 쓰레드들의 실행과 스케줄링을 관리해주는 것은 사실상 시스템입니다. (사용가능한 코어에서 실행해주거나, 우선순위를 지키기 위해 인터럽팅을 한다든가 등등)

기술적인 관점에서 볼 때, 쓰레드는 커널 레벨과 어플리케이션 레벨에서 코드 실행을 관리하기 위한 데이터 스트럭쳐 (자료 구조) 의 조합 (combination) 입니다. 커널 레벨의 스트럭쳐는 이벤트를 쓰레드에 디스패칭 시키는 것과 쓰레드를 현재 사용가능한 코어에 스케줄링 시키는 것을 조정합니다. 어플리케이션 레벨의 스트럭쳐에는 함수 콜을 저장하기 위한 콜 스택과 쓰레드의 속성 및 상태를 관리할 때 필요한 스트럭처가 포함됩니다.

non-concurrent 어플리케이션에서는 실행가능한 쓰레드가 오직 한 개 있습니다. 그 쓰레드는 어플리케이션의 메인 루틴과 함께 시작하고 끝나며, 어플리케이션의 전반적인 동작을 구현하기 위한 메서드 혹은 함수를 하나 하나씩 처리합니다. 반면, 동시성 (concurrency) 을 보장받는 어플리케이션은 쓰레드 하나로 시작하되 필요에 따라 쓰레드를 더 추가하게 됩니다. 각각의 쓰레드는 어플리케이션의 메인 루틴과 독립적인 각각의 시작루틴을 가집니다. 어플리케이션이 여러 개의 쓰레드 (Multiple threads) 를 가지는 것은 두 가지 큰 장점이 있습니다:

  • 반응성을 향상시킬 수 있습니다.

  • 멀티코어 시스템에서의 실시간 성능 (real-time performance) 을 향상시킬 수 있습니다.

만약 어플리케이션이 오직 하나의 쓰레드만 이용한다고 생각해봅시다. 그 쓰레드는 모든 것을 혼자 떠맡게 될 것입니다. 이벤트에 응답하거나, UI를 갱신하거나, 계산작업을 처리하는 것 등을요. 이때 진짜 문제는 하나의 쓰레드가 한 번에 하나의 일 밖에 처리하지 못한다는 사실입니다. 이 사실을 토대로 한번 상상해봅시다. 만약 처리해야 할 계산 중 하나가 엄청나게 긴 시간을 잡아먹는다면 어떻게 될까요? 싱글 쓰레드가 그 계산을 하는 동안 어플리케이션은 유저 인터랙션도 UI 갱신도 처리할 수 없는 벽돌 상태가 될 것입니다. 사용자는 어플리케이션이 멈췄다고 생각해서 강제종료를 할지도 모르죠. 메인 쓰레드 대신 다른 쓰레드에게 이 긴 계산을 맡긴다면, 계산을 하는 와중에도 어플리케이션은 유저의 인터랙션에 반응할 수 있습니다. 이런 것이 멀티 쓰레드의 장점 중 하나입니다.

요즘은 일반적으로 멀티코어가 제공되며, 멀티 쓰레드를 통해 어플리케이션의 성능을 향상시킬 수 있습니다. 쓰레드 각각은 동시에 다른 프로세서 코어에서 다른 테스크를 수행하면서 주어진 시간 내에 싱글 쓰레드보다 더 많은 일을 해냅니다.

물론 쓰레드가 어플리케이션의 성능 문제를 모두 해결할 수 있는 것은 아닙니다. 쓰레드의 장점과 함께 잠재적인 문제도 함께 딸려오게 됩니다. 실행 경로 (paths of excution) 를 여러 개 가지면 그만큼 코드가 복잡해질 수 있습니다. 각 쓰레드는 다른 쓰레드와 충돌나지 않도록 주의를 기울여야하며, 모든 쓰레드가 동일한 메모리 공간을 공유한다는 것도 유의해야 합니다. 만약 두 개의 쓰레드가 동시에 같은 데이터 스트럭쳐에 접근하여 새로운 값을 할당하려 한다면, 하나가 다른 하나의 값을 덮어쓰게 될 것입니다. 이런 케이스를 코드에서 잘 방어했다고 하더라도, 컴파일러 최적화가 발생시키는 버그도 있으므로 여전히 주의를 기울여야 합니다.



용어설명

  • Thread : separate path of execution for code

  • Process : running executable, which can encompass multiple threads

  • Task : work that needs to be performed



Alternatives to Threads

쓰레드를 생성하면 코드에 불확실성이 추가될 위험이 있습니다. 쓰레드는 어플리케이션에 동시성 (concurrency) 을 추가하기 위한 방법들 중 비교적 로우 레벨이며 복잡한 방법입니다. 만약 제대로 이해하지 않은 상태에서 쓰레드 코드를 작성하게 된다면 동기화 이슈나 타이밍 이슈 등의 여러 위험에 처할 가능성이 높아집니다.

당신은 멀티 쓰레드와 동시성이 정말로 필요한지를 고려해보아야 합니다. 멀티 쓰레드는 동일 프로세스 내에서 여러 코드를 실행하는 방법을 확실히 제공합니다만, 프로세스에 엄청난 오버헤드를 가져올 수 있습니다. 메모리 사용량과 CPU 타임 두 가지 측면 모두에 있어서요. 이 오버헤드는 당신이 목적한 테스크에 비해 너무 클 수도 있습니다. 이런 오버헤드를 감수하지 않아도 (멀티 쓰레드를 사용하지 않아도) 똑같은 목적을 달성할 수 있는 쉬운 옵션들을 몇 가지 소개합니다.

멀티 쓰레드의 대안기술들과 싱글 쓰레드를 효율적으로 쓸 수 있는 방법들을 소개합니다.

Technology

Description

Operation objects

Operation 객체는 일반적으로 보조 쓰레드에서 실행될 테스크에 대한 래퍼 (wrapper) 입니다. 당신이 테스크 자체에만 집중할 수 있도록 테스크의 실행에 대한 쓰레드 관리를 내부로 숨겨줍니다. 보통은 Operation Queue 객체와 함께 사용됩니다.

NSOperation 객체 등이 여기에 해당됩니다. 어떻게 사용하는지 더 자세한 정보를 얻고 싶다면 Concurrency Programming Guide 를 참조하세요.

GCD

Grand Central Dispatch (GCD) 는 쓰레드 관리가 아닌 수행할 테스크에 초점을 맞출 수 있게 도와줍니다. GCD를 사용한다면 당신은 그저 원하는 테스크를 정의하고 그것을 work queue 에 넣으면 됩니다. 그러면 queue 에서는 알맞은 쓰레드에 테스크를 스케줄링 시키고, 사용가능한 코어 개수와 테스크를 실행시키기 위한 현재 부하를 고려해줍니다. 당신이 직접 멀티 쓰레드를 사용하는 것보다 더 효율적인 방법으로요.

GCD와 work queue에 대한자세한 정보를 얻고 싶다면 Concurrency Programming Guide 를 참조하세요.

Idle-time notifications

비교적 짧고 우선순위가 낮은 테스크는 idle-time 노티피케이션을 활용하여 어플리케이션이 busy 상태가 아닐 때 수행시킬 수 있습니다. Cocoa는 NSNotificationQueue 객체를 통해 idle-time 노티피케이션을 서포트합니다. Default NSNotificationQueue 객체에 NSPostWhenIdle 옵션을 걸어 노티피케이션을 post 하면 idle-time 노티피케이션이 request 됩니다. 이제 queue 는 런 루프가 idle이 될 때까지 기다렸다가 해당 노피티케이션을 전달합니다.

더 자세한 정보는 Notification Programming Topics 를 참조하세요.

Asynchronous functions

시스템 인터페이스는 자동 동시성을 제공하는 많은 Asynchronous 함수들을 제공합니다. 이런 API들은 아마 시스템 데몬과 프로세스를 사용하거나 커스텀 쓰레드를 생성할 것입니다. 그러나 실제로 어떻게 구현되어 있는지 당신은 신경쓰지 않아도 괜찮습니다. Synchronous 함수나 커스텀 쓰레드를 사용하는 대신에 이런 Asynchronous 함수들을 한번 고려해보세요. 

Timers

쓰레드를 사용하기에는 너무 사소하지만 주기적으로 수행이 되어야 할 테스크를 수행하기 위해, 메인 쓰레드에 Timer를 등록해둘 수 있습니다.

더 자세한 정보는 Timer Sources 를 참조하세요.

Separate Processes

테스크가 어플리케이션과 별로 관계가 없는 경우에는 (비록 쓰레드보다 무겁긴 하지만) Separate Process 를 생성하는 것이 도움이 될 것입니다. 테스크가 상당한 메모리 공간을 필요로 하거나 루트 권한을 가져야 한다면 프로세스를 사용하십시오. 예를 들면 32 bit 어플리케이션이 유저에게 결과를 보여주어야 하는 큰 데이터 집합의 계산이 있는데, 이 계산에 64 bit 서버 프로세스를 사용하는 것입니다.


<Warning> fork 함수를 통해 Separate process 를 시작할 때는, 반드시 fork 호출과 더불어 exec (혹은 그 유사한 함수) 호출을 해야 합니다.



Threading Support

Threading Packages

쓰레드의 기본 구현 매커니즘은 Mach threads 이긴 하지만, Mach 레벨에서 쓰레드를 사용해 볼 일은 거의 없을 것입니다. 그 대신 좀 더 편리한 POSIX API 혹은 그 파생들을 사용할 것입니다.

다음은 어플리케이션에서 사용할 수 있는 쓰레드 기술의 리스트입니다.

Technology

Description

Cocoa threads

Cocoa 는 NSThread 클래스를 이용하여 쓰레드를 구현했습니다. 또한 새로운 쓰레드를 만들거나 이미 돌아가는 쓰레드에서 코드를 실행해주는 메서드도 제공합니다.

자세한 정보는 Using NSThread 와 Using NSObject to Spawn a Thread 를 참조하세요.

POSIX threads

POSIX 쓰레드는 C-based 인터페이스를 제공합니다. Cocoa 어플리케이션 외의 어플리케이션을 작성한다면 이것을 사용하는 것이 최적의 선택일 것입니다.

자세한 정보는 Using POSIX Threads 를 참조하세요.

Multiprocessing Services

멀티프로세싱 서비스는 Max OS old version 에서 사용되는 legacy C-based 인터페이스입니다. 이 기술은 OS X 에서만 사용가능하며, 새로운 발전을 위해 피하는 것이 좋습니다.


어플리케이션 레벨에서, 모든 쓰레드는 본질적으로 같은 방식으로 동작합니다. 쓰레드가 시작된 이후, 쓰레드는 다음 세 가지 main state 중 하나의 state가 됩니다. running, ready, blocked 입니다. 쓰레드가 현재 running 상태가 아니라면 (1) blocked 되어서 input 을 기다리고 있거나 (2) ready 중이지만 아직 스케줄되지 않았거나, 둘 중 하나입니다. 쓰레드는 exit 되어 terminated state가 되기 전까지는, 이런 state들을 반복해서 왔다갔다 합니다.

새로운 쓰레드를 생성할 때는 반드시 쓰레드의 entry-point 함수를 지정해야 합니다. (Cocoa 쓰레드의 경우에는 entry-point 메서드) 이 entry-point 함수에는 쓰레드에서 돌리고자 할 코드를 넣어둡니다. 이 함수가 return 될 때, 혹은 쓰레드를 명시적으로 terminate 시켰을 때, 쓰레드는 영구적으로 멈추고 시스템에 의해 회수됩니다. 쓰레드를 생성하려면 메모리와 시간이 비교적 많이 소요되는 편이기 때문에, entry point 함수에서는 상당한 양의 작업을 하거나 주기적으로 반복될 작업을 하는 것이 추천됩니다.

사용가능한 쓰레드 기술에 대해 더 많은 정보가 필요하면 Thread Management 를 참조해주세요.


Run Loops

Run loop 는 쓰레드에 비동기적으로 도착하는 이벤트를 관리하는 데에 사용되는 인프라의 조각입니다. Run loop 는 쓰레드에 대한 하나 또는 그 이상의 이벤트 소스를 모니터링하는 방식으로 동작합니다. 이벤트가 도착하면, 시스템은 쓰레드를 깨우고 run loop 에 이벤트를 디스패치 시킵니다. 만약 어떤 이벤트도 존재하지 않을 경우 run loop 는 쓰레드를 sleep 시킵니다.

생성한 모든 쓰레드에 대해 run loop 를 사용할 필요는 없지만 그렇게 하는 것이 유저에게는 더 편안함을 줄 수 있습니다. Run loop 는 최소한의 리소스를 사용하는 수명이 긴 쓰레드를 만들 수 있게 해줍니다. 쓰레드가 할 일이 없을 때는 run loop 가 그 쓰레드를 sleep 상태로 만들기 때문에, 폴링하고 있을 필요가 없습니다. (폴링은 CPU 싸이클을 낭비하며 프로세서를 sleep 상태로 만들지 못하게 합니다)

Run loop 를 구성 (configure) 하기 위해 당신은 쓰레드를 실행시키고, run loop 오브젝트를 참조시키고, 당신의 이벤트 핸들러를 설정하고, run loop 에게 run 을 명령해야 합니다. OS X 가 제공하는 인프라는 메인 쓰레드에 있어서의 이런 설정을 자동으로 구성해줍니다. 그러나 당신이 메인 쓰레드 외의 보조 쓰레드를 이렇게 만들고 싶다면 그것은 자동으로 되지 않는다는 것을 명심하세요.

Run loop 와 그 사용 예제에 대해 자세히 알고 싶다면 Run Loops 를 참조하세요. 


Synchronization Tools

쓰레드 프로그래밍의 위험 중 하나는 쓰레드들 간의 리소스 쟁탈입니다. 만약 여러 개의 쓰레드가 동시에 같은 리소스를 사용하거나 변경하려 한다면 문제가 발생합니다. 문제를 완화할 방법 중 하나로써 공유 자원을 완전히 없애고 각각의 쓰레드가 자신만의 리소스 Set을 가지게 하는 방법이 있습니다. 이런 방법이 불가능할 경우에는 lock, condition, atomic operation 등을 통해 리소스 접근에 대한 싱크를 맞추어야 합니다.

Lock 은 한 번에 오직 하나의 쓰레드에서만 실행되어야 할 코드를 보호해줍니다. Lock 의 가장 일반적인 타입은 mutual exclusion lock, 즉 mutex 입니다. 쓰레드 A 는 현재 다른 쓰레드 B 에서 가지고 있는 mutex 를 획득할 수 없으며, 쓰레드 B 가 mutex 를 릴리즈 할 때까지 기다려야 합니다. 더 많은 정보가 필요하면 Locks 를 참조하세요.

Task 의 적절한 순서를 보장해주기 위해 시스템은 lock 외의 것들도 지원해줍니다. 그 중 하나가 Condition 입니다. 이것은 문지기 같은 역할을 해줍니다. 특정 condition 을 만족하기 전까지 쓰레드를 blocking 합니다.

Data를 보호하고 접근에 대한 동시성을 보장하기 위해서는 위에서 설명한 Lock 과 Condition 이 일반적으로 사용되는 방법입니다만, Atomic operation 이라는 다른 방법을 사용할 수도 있습니다. Atomic operation 은 Scalar data 를 가지고 수학적/논리적 연산을 수행할 때에 Lock 에 비하여 쉬운 대안이 될 수 있습니다. 변수에 대한 수정이 완료되기 전까지는 다른 쓰레드에서 그 변수에 접근하지 못하도록 하기 위해, Atomic operation 은 특별한 하드웨어 명령을 사용합니다.

사용가능한 Synchronization Tool 을 더 알아보려면 Synchronization Tools 을 참조하세요.


Inter-thread Communication

쓰레드 프로그래밍을 하다보면 특정 시점에서 쓰레드 간 통신이 필요한 순간이 오기 마련입니다. (쓰레드의 역할은 어플리케이션을 위해 특정 작업을 수행하는 것인데, 수행의 결과가 아무 곳에도 쓰이지 않는다면 무슨 의미가 있을까요?) 예를 들어 쓰레드가 새로운 Job request 를 처리하거나, 자신의 진행상황을 메인 쓰레드에 보고할 때, 하나의 쓰레드에서 다른 쓰레드로 정보를 전달할 방법이 필요합니다. 다행히도 쓰레드는 같은 Process space 를 공유합니다. 즉슨 쓰레드 간 통신을 위한 많은 옵션이 있다는 것입니다.

쓰레드 간의 통신을 위한 방법들은 각각의 장점과 단점을 가집니다. 이 중 Thread-Local 저장소를 구성하는 것은 OS X 에서의 가장 일반적인 통신 메커니즘입니다. 아래 테이블은 복잡성이 증가하는 순서대로 나열한 쓰레드 간 통신 방법 리스트입니다.

Mechanism

Description

 Direct messaging

Cocoa application 은 현재 쓰레드가 아닌 다른 쓰레드에서 perform selector 를 할 수 있도록 지원합니다. 즉, 다른 쓰레드에서 메서드를 execute 할 수 있습니다. 이 경우 타겟 쓰레드의 context 에서 execute 되기 때문에, 메시지는 자동적으로 serialized 되어 전송됩니다. input sources 에 대한 자세한 정보는 Cocoa Perform Selector Sources 를 참조하세요.

Global variables, shared memory, and objects

글로벌 변수를 사용하거나 오브젝트/메모리 블럭을 공유하는 것도 빠르고 심플한 방법입니다.

그러나 공유 변수는 direct messaging 에 비해 허술한 부분이 있습니다. Race condition 이나 데이터의 손상을 막기 위해, 공유 변수는 반드시 lock, synchronization 메커니즘 등으로 보호되어야 합니다.

Conditions

Conditions 는 쓰레드가 코드의 특정 부분을 실행하는 것을 컨트롤하고 싶을 때 사용하는 synchronization tool 입니다. 특정 조건에 부합할 때만 쓰레드가 run 되도록 허락해주는 문지기라고 생각하면 될 것입니다. 사용법에 대해 알고 싶다면 Using Conditions 를 참조하세요.

Run loop sources

Custom run loop source 는 쓰레드에서 Application-specific message 를 받기 위한 하나의 방법입니다. 이것은 이벤트에 반응하기 때문에, 만약 아무 일도 없다면 쓰레드의 효율을 위해 자동으로 sleep 에 들어갑니다. run loops 와 run loop sources 에 대한 더 많은 정보는 Run Loops 를 참조하세요.

Ports and sockets

포트 기반 통신은 쓰레드 간 통신을 위한 정교하고 안정적인 기술입니다. 중요한 점은, 포트와 소켓은 다른 프로세스와 서비스 등의 외부 개체와 통신할 때 사용할 수 있다는 것입니다. 효율을 위하여 포트는 run loop source 를 이용하여 구현되고, 따라서 포트에서 waiting 중인 데이터가 없을 때는 sleep 상태가 됩니다. run loop 와 포트 기반 input source 에 대한 더 자세한 정보는 Run Loops 를 참조하세요.

Message queues

기존의 멀티프로세싱 서비스는 incoming, outgoing data 를 관리할 때 FIFO (first-in, first-out) Queue abstraction 을 사용했습니다. Message queues 는 심플하고 편리하지만, 다른 통신 기술에 비해 효율적인 편은 아닙니다. Message queues 를 사용하는 방법을 더 알고 싶다면, Multiprocessing Services Programming Guide 를 참조하세요.

Cocoa distributed objects

Distributed objects 는 포트 기반 통신에 대한 구현을 high-level 로 제공하는 Cocoa 기술입니다. 이 기술을 쓰레드 간 통신에 사용할 수는 있지만, 그렇게 하려면 오버헤드가 상당하여 녹록지는 않을 것입니다. Distributed object 는 다른 프로세스와 통신할 때 사용하는 것이 더 적합합니다. 더 많은 정보는 Distributed Objects Programming Topics 를 참조하세요.



Design Tips

Avoid Creating Threads Explicitly

쓰레드를 명시적으로 생성하는 것은 잠재적 오류를 낳을 가능성이 커지게 되니 웬만하면 피해야 합니다. OS X 와 iOS 에는 동시성을 보장해주는 여러 API 들이 있습니다. 쓰레드를 직접 생성하는 것보다는 이런 API 들 (asynchronous APIs, GCD, operation objects, ...) 의 사용을 고려해보세요. 이런 기술들은 내부적으로 thread-related work 를 수행하고, 동시성을 보장해줍니다. 또한 GCD, operation objects 같은 기술들은 당신이 직접 코드로 하는 것보다 더 효율적으로 쓰레드들이 관리될 수 있도록 설계되었습니다. GCD와 operation objects 에 대한 더 자세한 정보는, Concurrency Programming Guide 를 참조하세요.


Keep Your Threads Reasonably Busy

쓰레드를 직접 생성하고 관리하기로 결정했다면, 쓰레드가 귀중한 시스템 리소스를 소비한다는 것을 염두에 두세요. 쓰레드에 맡기는 task 는 합리적으로 수명이 길고 생산적인 것이어야 합니다. 또한 오랜 시간을 idle 상태로 보내는 쓰레드를 종료시키는 것을 두려워하지 마세요. 쓰레드는 적지 않은 양의 메모리를 사용합니다. 따라서 idle 쓰레드를 release 하는 것은 어플리케이션의 메모리 절약에도 도움이 될 뿐만 아니라 다른 시스템 프로세스가 사용할 물리적 메모리를 해제하는 것이기도 합니다.

* 주의: idle 쓰레드를 종료하기 전에, 어플리케이션의 현재 성능에 대한 기준선 측정결과를 항상 기록해두세요. 그 후 쓰레드 종료 후 다시 측정하여 값을 비교해보세요. 실제로 성능이 향상되었는지 아니면 그 반대인지 확인해보세요.


Avoid Shared Data Structures

쓰레드 관련 리소스 충돌을 막는 가장 단순하고 쉬운 방법은 각 쓰레드마다 그것이 필요로 하는 데이터를 복사해서 주는 것입니다. 병렬 수행 코드는 쓰레드 사이의 통신과 리소스 쟁탈을 최소화 할수록 잘 동작합니다.

멀티쓰레드 어플리케이션을 만드는 것은 쉬운 일이 아닙니다. 당신이 아무리 신경써서 공유 데이터를 적절한 시점에 lock 하더라도, 당신의 코드는 안전하다고 말할 수 없습니다. 예를 들어, 특별한 순서에 따라 변경되어야 하는 공유 데이터가 있다고 한다면, 코드에서 문제가 발생할 가능성이 있는 것입니다. 이것을 보완하기 위해 transaction-based model 을 사용하도록 코드를 바꾸는 것은, 사실상 멀티 쓰레드의 성능적인 장점을 포기하게 되는 것일 수도 있습니다. 우선 리소스 쟁탈을 아예 하지 않도록 해보면 (데이터를 각각 copy하여 전달하는 방식 등을 사용하여) 때로는 그것이 더 심플하고 훌륭한 성능을 내기도 합니다.


Threads and Your User Interface

그래픽 유저 인터페이스(이하 UI)가 있는 어플리케이션을 만든다면, 유저의 이벤트를 받고 UI를 업데이트 하는 것을 메인 쓰레드에서 하는 것을 추천합니다. 이런 접근법은 윈도우를 그리는 것과 유저 이벤트를 처리하는 것 사이의 synchronization 이슈를 해결하는 데에 도움이 될 것입니다.  Cocoa 같은 특정 프레임워크들은 일반적으로 이런 동작을 필요로 합니다. 또한 꼭 이런 이유가 아니라도, 메인 쓰레드에서 UI를 업데이트 하는 것은 UI 관리로직을 단순화 하는 데에 도움이 됩니다.

그래픽 operation 을 메인 쓰레드가 아닌 다른 쓰레드에서 수행하는 것이 더 유리한 몇 가지 예외 케이스도 있습니다. 예를 들어 이미지를 생성 처리하고 그에 관련된 계산을 하는 작업은 보조 쓰레드에 맡길 수 있습니다. 보조 쓰레드에게 이런 작업을 맡기는 것은 퍼포먼스를 놀라울 정도로 증가시킵니다.

Cocoa thread safety 에 관해 더 알고 싶다면 Thread Safety Summary 를 참조하세요. Cocoa 프레임워크에서의 drawing 에 대해 더 알고 싶다면 Cocoa Drawing Guide 를 참조하세요.


Be Aware of Thread Behaviors at Quit Time

non-detached 쓰레드들이 전부 종료될 때까지 프로세스는 실행됩니다. 어플리케이션의 메인 쓰레드만이 non-detached 쓰레드로 만들어지는 것이 디폴트지만, 다른 쓰레드들도 그렇게 만들 수는 있습니다. 유저가 어플리케이션을 종료했을 때, detached 쓰레드들은 즉각 종료하는 것이 올바른 동작일 것입니다. detached 쓰레드들이 하는 작업은 optional 한 것으로 여겨지기 때문입니다. 만약 데이터를 디스크에 저장하는 것 같은 중요한 작업을 백그라운드 쓰레드가 하고 있다면, 어플리케이션이 종료됐을 때 데이터가 손실되지 않도록 이 쓰레드를 non-detached 로 생성해야 할 것입니다.

Cocoa 어플리케이션의 경우 applicationShouldTerminate: 라는 딜리케이트 메서드를 사용하여 어플리케이션의 종료를 지연시킬 수 있습니다. 종료를 지연시킬 때, 어플리케이션은 중요한 쓰레드들이 각자의 일을 끝낼 때까지 기다렸다가 replyToApplicationShouldTerminate: 메서드를 invoke 시켜야 할 것입니다. 이런 메서드들에 대한 자세한 정보는 NSApplication Class Reference 를 참조하세요.


Handle Exceptions

예외처리(Exception handling) 매커니즘은 Exception 이 발생한 순간의 콜스택에 의존하고 있습니다. 콜스택은 예외 발생 시 적절한 clean up 을 수행합니다. 각 쓰레드가 자신만의 콜스택을 가지고 있기 때문에, 각 쓰레드는 각각 자신의 exception을 catch 해야 하는 책임을 지닙니다. 하나의 쓰레드에서 다른 쓰레드에서 발생하는 exception 까지 잡아서 처리하는 것은 불가능하며, 보조 쓰레드에서 exception catch 를 실패하나, 메인 쓰레드에서 exception catch 를 실패하나, process 가 종료되는 것은 똑같습니다.

만약 현재 쓰레드에서의 exception 발생을 다른 쓰레드(ex:메인 쓰레드)에 알려야 한다면, 현재 쓰레드에서 다른 쓰레드로 exception 상황에 대한 정보를 전달해야 할 것입니다. exception 을 잡은 쓰레드는 현재 상황에 따라 process 를 계속 진행하든지, 명령을 기다리든지, 단순히 어플리케이션을 종료하든지 결정하면 됩니다.

<Note> Cocoa에서 NSException 객체는 독립적인(self-contained) 객체입니다. 따라서 한 번 잡히면 하나의 쓰레드에서 다른 쓰레드로 전달될 수 있습니다.

특정 상황에서 exception handler 는 자동으로 생성되기도 합니다. 예를 들어 Objective-C에서 @synchronized 지시어를 붙이는 것은 implicit exception handler 를 포함시키는 것과 마찬가지입니다.


Terminate Your Threads Cleanly

쓰레드를 정상적으로 종료시키는 가장 좋은 방법은 쓰레드가 routine의 최종점까지 이르게 하는 것입니다. 쓰레드를 즉각 종료시키는 함수들은 최후의 수단으로만 사용되어야 합니다. 쓰레드가 natural end point 까지 이르기 전에 임의로 종료되어 버리면 깔끔한 cleaning up 을 할 수 없게 됩니다. 그런 쓰레드가 점유하고 있던 메모리나 다른 자원들을 되찾지 못하게 될 것이고, 이것은 메모리 누수 같은 문제들을 야기하게 됩니다.

쓰레드를 종료하는 적절한 방법에 대한 더 많은 정보는 Terminating a Thread 를 참조하세요.


Thread Safety in Libraries

어플리케이션 개발자는 어플리케이션에서의 멀티 쓰레드의 실행을 통제하지만, 라이브러리 개발자는 그것을 하지 않습니다. 라이브러리를 개발할 때는, 이 라이브러리를 호출하는 어플리케이션이 멀티쓰레드 상황에 있거나, 그렇지 않더라도 언제든 멀티쓰레드로 전환될 수 있다는 것을 반드시 가정해야 합니다. 따라서 코드의 critical section 에 항상 lock 을 사용해야 합니다.

'iOS 일반 > Apple Guide' 카테고리의 다른 글

Thread Management  (0) 2017.08.17
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함