프로그래밍/iOS

Concurrency Programming Guide (6) - Migrating Away from Threads

병인 2020. 12. 27. 01:30

Grand Central Dispatch 및 operation object를 활용하기 위해 기존 스레드 코드를 전환하는 방법에는 여러 가지가 있다. 모든 경우에 스레드로부터 멀어 질 수는 없지만 전환을 수행하는 장소에서 성능(및 코드의 단순성)이 크게 향상 될 수 있다. 특히 스레드 대신 dispatch queue와 operation queue를 사용하면 다음과 같은 몇 가지 이점이 있다.

 

  • 애플리케이션의 메모리 공간에 스레드 스택을 저장하기 위해 드는 메모리 부담을 줄일 수 있다.
  • 스레드를 만들고 구성하는 데 필요한 코드를 제거할 수 있다.
  • 스레드에 대한 작업을 관리하고 스케줄링하는 데 필요한 코드를 제거할 수 있다.
  • 작성해야 하는 코드를 단순화할 수 있다.

 

이번 챕터에서는 기존 thread-based code를 대체하고 대신 dispatch queue와 operation queue를 사용하여 동일한 유형의 동작을 수행하는 방법에 대한 몇 가지 팁과 지침을 제공한다.

 

Replacing Threads with Dispatch Queues

 

스레드를 dispatch queue로 대체하는 방법을 이해하려면 먼저 현재 애플리케이션에서 스레드를 사용할 수 있는 몇 가지 방법을 생각해보자.

 

  • Single task thread. 단일 태스크를 수행하는 스레드를 만들고 태스크가 완료되면 스레드를 릴리즈 한다.
  • Worker thread. 각 태스크에 대해 특정 태스크를 염두에 두고 하나 이상의 작업자 스레드를 만든다. 주기적으로 각 스레드에 태스크를 dispatch 한다.
  • Thread pool. 일반 스레드 풀을 만들고 각 스레드에 대해 run loop를 설정한다. 수행 할 태스크가 있으면 풀에서 스레드를 가져 와서 작업을 전달한다. 사용 가능한 스레드가 없으면 작업을 큐에 넣고 스레드를 사용할 수 있을 때까지 기다린다.

 

이것은 극적으로 다른 기술처럼 보일 수 있지만 실제로는 동일한 원리의 변형에 불과하다. 각각의 경우 스레드는 애플리케이션이 수행해야 하는 일부 태스크를 실행하는 데 사용된다. 그들 사이의 유일한 차이점은 스레드를 관리하는 데 사용되는 코드와 태스크 큐이다. dispatch queue와 operation queue을 사용하면 모든 스레드 및 스레드 통신 코드를 제거하고 대신 수행하려는 태스크에만 집중할 수 있다.

 

위의 스레딩 모델 중 하나를 사용하는 경우 애플리케이션이 수행하는 태스크 유형에 대해 이미 잘 알고 있어야 한다. 사용자 지정 스레드 중 하나에 태스크를 전달하는 대신 해당 태스크를 operation object 또는 블록 오브젝트에 캡슐화하고 적절한 큐로 전달해라. 특히 논쟁의 여지가 없는 태스크, 즉 잠금을 사용하지 않는 태스크의 경우 다음과 같은 직접 변경을 수행 할 수 있어야 한다.

 

  • single task thread의 경우 태스크를 블록 또는 operation object에 캡슐화하고 concurrent queue에 전달한다.
  • worker thread의 경우 serial queue을 사용할지 concurrent queue을 사용할지 결정해야 한다. worker thread를 사용하여 특정 태스크 집합의 실행을 동기화하는 경우 serial queue을 사용한다. worker thread를 사용하여 상호 종속성 없이 임의의 태스크를 실행하는 경우 concurrent queue을 사용한다.
  • thread pool의 경우 태스크를 블록 또는 operation object에 캡슐화하고 실행을 위해 concurrent queue에 dispatch한다.

 

물론 이와 같은 간단한 변경은 모든 경우에 작동하지 않을 수 있다. 실행중인 태스크가 공유 리소스에 대해 경쟁하는 경우 이상적인 솔루션은 먼저 해당 경쟁을 제거하거나 최소화하는 것이다. 공유 리소스에 대한 상호 종속성을 제거하기 위해 코드를 리팩터링하거나 재구성 할 수 있는 방법이 있다면 확실히 바람직하다. 그러나 그렇게 할 수 없거나 효율성이 떨어지는 경우 큐를 활용할 수 있는 방법이 있다. 큐의 큰 장점은 코드를 실행하는 더 예측 가능한 방법을 제공한다는 것이다. 이러한 예측 가능성은 잠금 또는 기타 무거운 동기화 메커니즘을 사용하지 않고 코드 실행을 동기화하는 방법이 여전히 있음을 의미한다. 잠금을 사용하는 대신 큐를 사용하여 많은 동일한 작업을 수행 할 수 있다.

 

  • 특정 순서로 실행해야 하는 태스크가 있는 경우 serial dispatch queue에 전달한다. operation queue을 사용하려면 operation object의 종속성을 사용하여 해당 오브젝트가 특정 순서로 실행되도록 한다.
  • 현재 잠금을 사용하여 공유 리소스를 보호하는 경우 serial queue를 만들어 해당 리소스를 수정하는 태스크를 실행한다. 그런 다음 serial queue는 동기화 메커니즘으로 기존 잠금을 대체한다. 잠금제거를 위한 자세한 기술 정보는 하단의 Eliminating Lock-Based Code을 참조하시오.
  • 스레드 조인을 사용하여 백그라운드 작업이 완료 될 때까지 기다리는 경우 대신 dispatch group을 사용하는 것이 좋다. NSBlockOperation 오브젝트 또는 operation object의 종속성을 사용하여 유사한 그룹 완성 동작을 달성 할 수도 있다. 자세한 내용은 하단의 Replacing Thread Joins을 참조하시오.
  • 생산자-소비자 알고리즘을 사용하여 finite resource 풀을 관리하는 경우 구현을 하단의 Changing Producer-Consumer Implementations에 표시된 것으로 변경하는 것이 좋습니다.
  • 스레드를 사용하여 descriptor에서 읽고 쓰거나 파일 작업을 모니터링하는 경우 Dispatch Source 문서에 설명 된 대로 Dispatch Source를 사용하시오.

 

큐는 스레드를 대체하는 만병 통치약이 아니다. 큐에서 제공하는 비동기 프로그래밍 모델은 지연 시간이 문제가 되지 않는 상황에 적합하다. 큐가 큐에 있는 태스크의 실행 우선 순위를 구성하는 방법을 제공하더라도 실행 우선 순위가 높다고해서 특정 시간에 태스크의 실행이 보장되는 것은 아니다. 따라서 스레드는 오디오 및 비디오 재생과 같이 지연 시간을 최소화해야 하는 경우 여전히 더 적절한 선택이다.

 

Eliminating Lock-Based Code

 

스레드 코드의 경우 잠금은 스레드 간에 공유되는 리소스에 대한 액세스를 동기화하는 전통적인 방법 중 하나이다. 그러나 잠금을 사용하는 데는 비용이 듭니다. 논란의 여지가 없는 경우에도 잠금 설정과 관련된 성능 불이익이 항상 있다. 그리고 논쟁의 여지가 있는 경우, 잠금이 릴리즈 되기를 기다리는 동안 하나 이상의 스레드가 불확실한 시간 동안 차단 될 가능성이 있다.

 

lock-based code를 큐로 바꾸면 잠금과 관련된 많은 불이익이 제거되고 나머지 코드도 단순화된다. 잠금을 사용하여 공유 리소스를 보호하는 대신 큐를 생성하여 해당 리소스에 액세스하는 작업을 직렬화 할 수 있다. 큐는 잠금과 동일한 패털티를 부과하지 않는다. 예를 들어, 태스크를 큐에 넣기 위해, 뮤텍스를 획득해서 커널로 트래핑 할 필요가 없다.

 

태스크를 큐에 넣을 때 해야 할 주요 결정은 동기식 또는 비동기식으로 수행할지 여부이다. 태스크를 비동기적으로 전달하면 태스크가 수행되는 동안 현재 스레드가 계속 실행된다. 작업을 동기적으로 전달하면 작업이 완료 될 때까지 현재 스레드가 차단된다. 두 옵션 모두 적절하게 사용되지만 가능할 때마다 태스크를 비동기적으로 전달하는 것이 확실히 유리하다.

 

다음 섹션에서는 기존 lock-based code를 동등한 queue-based code로 바꾸는 방법을 보여준다.

 

Implementing an Asynchronous Lock

 

비동기 잠금은 해당 리소스를 수정하는 코드를 차단하지 않고 공유 리소스를 보호하는 방법이다. 코드에서 수행하는 다른 작업의 부작용으로 데이터 구조를 수정해야 할 때 비동기 잠금을 사용할 수 있다. 전통적인 스레드를 사용하여 일반적으로 이 코드를 구현하는 방법은 공유 리소스에 대한 잠금을 설정하고, 필요한 변경을 수행하고, 잠금을 해제하고, 작업의 주요 부분을 계속하는 것이다. 그러나 dispatch queue를 사용하면 호출 코드가 변경이 완료 될 때까지 기다리지 않고 비동기적으로 수정할 수 있다.

 

하단의 코드는 비동기 잠금 구현의 예를 보여준다. 이 예에서 보호된 리소스는 own serial dispatch queue을 정의한다. 호출 코드는 리소스를 수정해야하는 수정 사항이 포함 된 블록 객체를이 큐에 전달한다. 큐 자체가 블록을 직렬로 실행하기 때문에 리소스 변경은 수신 된 순서대로 이루어진다. 그러나 태스크가 비동기적으로 실행 되었기 때문에 호출 스레드는 차단되지 않는다.

 

dispatch_async(obj->serial_queue, ^{
   // Critical section
});

 

Executing Critical Sections Synchronously

 

주어진 태스크가 완료 될 때까지 현재 코드를 계속할 수 없는 경우 dispatch_sync 함수를 사용하여 작업을 동기적으로 전달할 수 있다. 이 함수는 태스크를 dispatch queue에 추가 한 다음 태스크의 실행이 완료 될 때까지 현재 스레드를 차단한다. dispatch queue 자체는 필요에 따라 serial 또는 concurrent 큐가 될 수 있다. 이 함수는 현재 스레드를 차단하기 때문에 필요한 경우에만 사용해야 한다. 하단의 코드는 dispatch_sync를 사용하여 코드의 중요한 섹션을 래핑하는 기술을 보여준다.

 

dispatch_sync(my_queue, ^{
   // Critical section
});

 

이미 serial queue를 사용하여 공유 리소스를 보호하고 있는 경우 해당 큐에 동기적으로 dispatch하면 비동기적으로 dispatch하는 경우보다 더 이상 공유 리소스가 보호되지 않는다. 동기식으로 dispatch하는 유일한 이유는 중요 섹션이 완료 될 때까지 현재 코드가 계속되지 않도록 하는 것이다. 예를 들어 공유 리소스에서 값을 가져 와서 바로 사용하려면 동기적으로 dispatch해야 한다. 현재 코드가 중요 섹션이 완료 될 때까지 기다릴 필요가 없거나 단순히 추가 후속 작업을 동일한 serial queue에 제출할 수 있는 경우 일반적으로 비동기로 제출하는 것이 좋다.

 

Improving on Loop Code

 

코드에 루프가 있고 루프를 통해 매번 수행되는 작업이 다른 반복에서 수행되는 작업과 독립적인 경우 dispatch_apply 또는 dispatch_apply_f 함수를 사용하여 해당 루프 코드를 다시 구현하는 것을 고려할 수 있다. 이러한 함수는 루프의 각 반복을 처리를 위해 dispatch queue에 개별적으로 전달한다. concurrent queue와 함께 사용하면 이 기능을 사용하여 루프의 여러 반복을 동시에 수행 할 수 있다.

 

dispatch_apply 및 dispatch_apply_f 함수는 모든 루프 반복이 완료 될 때까지 현재 실행 스레드를 차단하는 동기 함수 호출이다. concurrent queue에 전달하면 루프 반복의 실행 순서가 보장되지 않는다. 각 반복을 실행하는 스레드가 차단되어 해당 반복이 주변의 다른 반복 전후에 완료되도록 할 수 있다. 따라서 각 루프 반복에 사용하는 블록 객체 또는 함수는 재진입해야 한다.

 

하단의 코드는 for 루프를 dispatch-based으로 동등하게 대체하는 방법을 보여준다. dispatch_apply 또는 dispatch_apply_f에 전달하는 블록 또는 함수는 현재 루프 반복을 나타내는 정수 값을 가져야한다. 이 예제에서 코드는 단순히 현재 루프 번호를 콘솔에 출력한다.

 

queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(count, queue, ^(size_t i) {
   printf("%u\n", i);
});

 

위의 예제는 단순하지만 dispatch queue를 사용하여 루프를 대체하는 기본 기술을 보여주었다. 그리고 이것이 loop-based 코드에서 성능을 향상시키는 좋은 방법이 될 수 있지만 여전히 이 기술을 신중하게 사용해야 한다. dispatch queue의 오버 헤드는 매우 낮지만 스레드에서 각 루프 반복을 예약하는 데는 여전히 비용이 발생하기 때문이다. 따라서 루프 코드가 비용을 보증할 만큼 충분한 작업을 수행하는지 확인해야 한다. 수행해야 하는 작업의 양은 성능 도구를 사용하여 측정해야 한다.

 

각 루프 반복에서 작업량을 늘리는 간단한 방법은 striding을 사용하는 것이다. striding을 사용하면 블록 코드를 다시 작성하여 원래 루프를 두 번 이상 반복한다. 그런 다음 dispatch_apply 함수에 지정한 카운트 값을 비례적으로 줄인다. 하단의 코드는 상단의 코드에 표시된 루프 코드에 대해 striding을 구현하는 방법을 보여준다. 하단의 코드에서 블록은 stride 값과 동일한 횟수로 printf 문을 호출한다. 이 경우에는 137이다. (실제 stride 값은 코드에서 수행하는 작업을 기반으로 구성해야 한다.) 총 반복 횟수를 stride value로 나눌 때 남은 나머지가 있기 때문에 나머지 반복은 인라인으로 수행된다.

 

int stride = 137;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_apply(count / stride, queue, ^(size_t idx){
    size_t j = idx * stride;
    size_t j_stop = j + stride;
    do {
       printf("%u\n", (unsigned int)j++);
    }while (j < j_stop);
});

size_t i;
for (i = count - (count % stride); i < count; i++)
   printf("%u\n", (unsigned int)i);

 

stride를 사용하면 몇 가지 확실한 성능 이점이 있다. 특히, stride는 stride에 비해 원래 루프 반복 횟수가 높을 때 이점을 제공한다. 동시에 더 적은 수의 블록을 dispatch 한다는 것은 해당 블록의 코드를 dispatch 하는 것보다 실행하는 데 더 많은 시간이 소요된다는 것을 의미한다. 그러나 다른 성능 메트릭과 마찬가지로 코드에서 가장 효율적인 값을 찾으려면 striding 값을 사용해야 할 수도 있다.

 

Replacing Thread Joins

 

스레드 조인을 사용하면 하나 이상의 스레드를 생성 한 다음 해당 스레드가 완료 될 때까지 현재 스레드가 대기하도록 할 수 있다. 스레드 조인을 구현하기 위해 상위 스레드는 조인 가능한 스레드로 하위 스레드를 생성한다. 상위 스레드가 하위 스레드의 결과 없이 더 이상 진행할 수 없으면 하위 스레드와 조인된다. 이 프로세스는 하위스레드가 태스크를 완료하고 종료 할 때까지 상위 스레드를 차단한다. 이 시점에서 상위 스레드는 하위 스레드에서 결과를 수집하고 자체 작업을 계속할 수 있다. 상위 스레드가 여러 하위 스레드와 조인해야 하는 경우 한 번에 하나씩 조인한다.

 

Dispatch group은 스레드 조인과 유사한 의미를 제공하지만 몇 가지 추가 이점이 있다. 스레드 조인과 마찬가지로 dispatch group은 하나 이상의 하위 작업이 실행을 마칠 때까지 스레드를 차단하는 방법이다. 스레드 조인과 달리 dispatch group은 모든 하위 작업을 동시에 대기한다. 그리고 dispatch group은 dispatch queue를 사용하여 작업을 수행하기 때문에 매우 효율적이다.

 

Dispatch group을 사용하여 결합 가능한 스레드가 수행하는 동일한 작업을 수행하려면 다음을 수행해야 한다.

 

  • dispatch_group_create 함수를 사용하여 새 dispatch group을 생성한다.
  • dispatch_group_async 또는 dispatch_group_async_f 함수를 사용하여 그룹에 태스크를 추가한다. 그룹에 전달하는 각 태스크는 참여 가능한 스레드에서 일반적으로 수행하는 작업을 나타낸다.
  • 현재 스레드가 더 이상 앞으로 진행할 수 없는 경우 dispatch_group_wait 함수를 호출하여 그룹을 기다린다. 이 함수는 그룹의 모든 태스크가 실행을 마칠 때까지 현재 스레드를 차단한다.

 

operation object를 사용하여 태스크를 구현하는 경우 종속성을 사용하여 스레드 조인을 구현할 수도 있다. 상위 스레드가 하나 이상의 태스크가 완료 될 때까지 기다리도록 하는 대신 상위 스레드의 코드를 operation object로 이동한다. 그런 다음 조인 가능한 스레드가 일반적으로 수행하는 작업을 수행하도록 설정된 상위 operation object와 여러 하위 operation object 간에 종속성을 설정한다. 다른 operation object에 대한 종속성이 있으면 모든 operation이 완료 될 때까지 상위 operation object가 실행되지 않는다.

 

dispatch group을 사용하는 방법의 예는 dispatch queue 문서에서 Waiting on Groups of Queued Tasks 섹션을 참조하시오. operation object 간의 종속성 설정에 대한 자세한 내용은 operation queue 문서에서 Configuring Interoperation Dependencies를 참조하시오.

 

Changing Producer-Consumer Implementations

 

producer-consumer model(생산자-소비자 모델)을 사용하면 동적으로 생성 된 한정된 수의 리소스를 관리 할 수 있다. 생산자가 새 리소스 (또는 작업)를 만드는 동안 한 명 이상의 소비자가 해당 리소스 (또는 작업)가 준비 될 때까지 기다렸다가 필요할 때 소비한다. 생산자-소비자 모델을 구현하기위한 일반적인 메커니즘은 상태 또는 semaphores이다.

 

생산자 스레드는 일반적으로 다음과 같은 상태를 수행한다.

 

  1. 상태와 관련된 뮤텍스를 잠금 한다 (pthread_mutex_lock 사용).
  2. 소비할 자원이나 작업을 생산한다.
  3. 소비할 것이 있음을 상태 변수에 signal을 보낸다 (pthread_cond_signal 사용).
  4. 뮤텍스를 잠금 해제한다 (pthread_mutex_unlock 사용).

 

다음 차례로 해당 소비자 스레드는 다음을 수행한다.

 

  1. 상태와 관련된 뮤텍스를 잠금 한다 (pthread_mutex_lock 사용).
  2. 다음을 수행하도록 while 루프를 설정하시오.
    • 정말로 해야 할 일이 있는지 확인하시오.
    • 수행 할 작업이 없거나 사용 가능한 리소스가 없는 경우 pthread_cond_wait를 호출하여 해당 signal이 발생할 때까지 현재 스레드를 차단한다.
  3. 생산자가 제공 한 작업 (또는 리소스)을 가져온다.
  4. 뮤텍스를 잠금 해제한다 (pthread_mutex_unlock 사용).
  5. 작업을 처리하시오.

 

dispatch queue을 사용하면 생산자와 소비자 구현을 단일 호출로 단순화 할 수 있다.

 

dispatch_async(queue, ^{
   // Process a work item.
});

 

생산자가 수행 할 작업이 있으면 해당 작업을 큐에 추가하고 큐가 항목을 처리하도록 하는 것이다. 이전 코드에서 변경되는 유일한 부분은 큐의 유형이다. 생산자가 생성 한 태스크를 특정 순서로 수행해야 하는 경우 serial queue를 사용한다. 생산자가 생성한 태스크를 동시에 수행 할 수 있는 경우 이를 concurrent queue에 추가하고 시스템이 가능한 한 많은 태스크를 동시에 실행하도록 한다.

 

Replacing Semaphore Code

 

현재 공유 리소스에 대한 액세스를 제한하기 위해 semaphore를 사용하고 있다면 dispatch semaphore를 대신 사용하는 것이 좋다. 전통적인 semaphore는 항상 semaphore를 테스트하기 위해 커널을 호출해야 한다. 반대로 dispatch semaphore는 사용자 공간에서 semaphore 상태를 빠르게 테스트하고 테스트가 실패하고 호출 스레드를 차단해야하는 경우에만 커널로 트랩한다. 이 동작으로 인해 dispatch semaphore가 충돌하지 않은 경우 기존 semaphore보다 훨씬 빠르다. 그러나 다른 모든 측면에서 디스패치 세마포어는 기존 semaphore와 동일한 동작을 제공한다.

 

dispatch semaphore의 사용 방법에 대한 자세한 내용은 dispatch queue 문서에 있는 Using Dispatch Semaphores to Regulate the Use of Finite Resources를 참고하시오.

 

Replacing Run-Loop Code

 

run loop를 사용하여 하나 이상의 스레드에서 수행되는 작업을 관리하는 경우 큐를 구현하고 계속 유지하는 것이 훨씬 더 간단하다는 것을 알 수 있다. custom run loop를 설정하려면 기본 스레드와 run loop 자체를 모두 설정해야 한다. run loop code는 하나 이상의 run loop source를 설정하고 해당 source에 도착하는 이벤트를 처리하기 위한 콜백 작성으로 구성된다. 모든 태스크 대신 serial queue를 만들고 태스크를 여기로 dispatch 할 수 있다. 따라서 모든 스레드 및 run loop 생성 코드를 한 줄의 코드로 바꿀 수 있다.

 

dispatch_queue_t myNewRunLoop = dispatch_queue_create("com.apple.MyQueue", NULL);

 

큐는 추가 된 모든 태스크를 자동으로 실행하므로 큐를 관리하는 데 필요한 추가 코드가 없다. 스레드를 생성하거나 구성 할 필요가 없으며 run loop source를 생성하거나 연결할 필요가 없다. 또한 큐에 태스크를 추가하기만 하면 큐에서 새로운 유형의 태스크를 수행 할 수 있다. run loop로 동일한 태스크를 수행하려면 기존 run loop source를 수정하거나 새 데이터를 처리 할 새 source를 생성해야 한다.

 

run loop의 일반적인 구성 중 하나는 네트워크 소켓에 비동기적으로 도착하는 데이터를 처리하는 것이다. 이러한 유형의 동작에 대한 run loop를 구성하는 대신 dispatch source를 원하는 큐에 연결할 수 있다. dispatch source는 또한 기존의 run loop source보다 더 많은 데이터 처리 옵션을 제공한다. 타이머 및 네트워크 포트 이벤트를 처리하는 것 외에도 dispatch source를 사용하여 파일을 읽고 쓰고, 파일 시스템 오브젝트를 모니터링하고, 프로세스를 모니터링하고, 시그널을 모니터링 할 수 있다. custom dispatch source를 정의하고 코드의 다른 부분에서 비동기적으로 트리거 할 수도 있다. dispatch source 설정에 대한 자세한 내용은 dispatch source 문서를 참조하시오.

 

Compatibility with POSIX Threads

 

Grand Central Dispatch는 사용자가 제공하는 태스크와 해당 태스크가 실행되는 스레드 간의 관계를 관리하기 때문에 일반적으로 태스크 코드에서 POSIX thread routine을 호출하지 않아야 한다. 어떤 이유로 호출을 해야 한다면 어떤 루틴을 호출 하는 것에 대해 매우 주의해야 한다. 이 섹션에서는 호출하기에 안전한 루틴과 대기중인 태스크에서 호출하기에 안전하지 않은 루틴을 표시한다. 이 목록은 완전하지는 않지만 호출하기에 안전한 것과 그렇지 않은 것을 표시한다.

 

일반적으로 애플리케이션은 생성하지 않은 오브젝트 또는 데이터 구조를 삭제하거나 변경해서는 안 된다. 따라서 dispatch queue를 사용하여 실행되는 블록 오브젝트는 다음 함수를 호출하지 않아야 한다.

 

  • pthread_detach
  • pthread_cancel
  • pthread_join
  • pthread_kill
  • pthread_exit

 

태스크가 실행되는 동안 스레드의 상태를 수정하는 것은 괜찮지만 태스크가 반환되기 전에 스레드를 원래 상태로 되돌려야 한다. 따라서 스레드를 원래 상태로 되돌리는 한 다음 함수를 호출하는 것이 안전하다.

 

  • pthread_setcancelstate
  • pthread_setcanceltype
  • pthread_setschedparam
  • pthread_sigmask
  • pthread_setspecific

 

주어진 블록을 실행하는 데 사용되는 기본 스레드는 호출마다 변경 될 수 있다. 결과적으로 애플리케이션은 블록 호출 사이에 예측 가능한 결과를 반환하는 다음 함수에 의존해서는 안 된다.

 

  • pthread_self
  • pthread_getschedparam
  • pthread_get_stacksize_np
  • pthread_get_stackaddr_np
  • pthread_mach_thread_np
  • pthread_from_mach_thread_np
  • pthread_getspecific

 

중요 : 블록은 그 안에서 발생하는 모든 language-level의 예외를 포착하고 억제해야 한다. 블록 실행 중에 발생하는 다른 오류는 블록에서 유사하게 처리하거나 애플리케이션의 다른 부분에 알리는 데 사용되어야 한다.

 

[원문]

developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/ThreadMigration/ThreadMigration.html

 

Migrating Away from Threads

Migrating Away from Threads There are many ways to adapt existing threaded code to take advantage of Grand Central Dispatch and operation objects. Although moving away from threads may not be possible in all cases, performance (and the simplicity of your c

developer.apple.com