study record

[Swift] Creating and Managing Dispatch Queues 본문

Swift/스위프트 정리

[Swift] Creating and Managing Dispatch Queues

asong 2023. 2. 13. 08:20

큐에 작업들을 넣기 전에, 어떤 큐를 사용할 것이고 사용하기 위한 목적을 결정해야 한다.

Dispatch queues는 직렬적으로 또는 병렬적으로 작업을 실행할 수 있다.

게다가, 만약 큐에 특별한 목적을 가진다면 큐의 속성들을 설정할 수 있다. 아래의 글을 통해 어떻게 dispatch queue를 만들고 사용을 위한 설정을 할 수 있는지를 보여준다.

 

Getting the Global Concurrent Dispatch Queues

concurrent dispatch queue는 병렬적으로 실행할 다양한 작업들을 가지고 있을 때 유용하다.

concurrent queue는 여전히 선입선출로 작동하는 큐이다. 다만, concurrent queue는 이전 작업이 끝나기 전에 추가적 작업이 빠져나올 수 있다. 

concurrent queue에 의해 실행되는 작업들의 실제 수는 가변적이고, 어플리케이션 변화의 상태에 따라 동적으로 변화한다. 많은 요인들이 실행되는 작업 수에 영향을 미친다. 예를 들어, 이용가능한 코어의 수, 다른 프로세스들에 의해 진행되고 있는 작업의 양, 직렬 dispatch queue에서 작업의 우선순위가 있다.

 

시스템은 네 가지 concurrent dispatch queue와 함께 각각의 어플리케이션을 제공한다. 이 큐들은 전역적이고, 우선순위 레벨에 따라 차별화된다. 그들이 전역적이므로 만들어낼 수는 없다. 대신에 dispatch_get_global_queue를 사용해서 큐 중 하나를 요청할 수 있다.

dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

 

게다가 디폴트 concurrent queue를 얻고, 높고 낮은 우선순위 레벨의 큐들을 DISPATCH_QUEUE_PRIORITY_LOW,HIGH를 통해서 얻을 수 있다. 또는 DISPATCH_QUEUE_PRIORITY_BACKGROUND도 있다. 예상한대로, 높은 우선순위의 작업은 디폴트보다 먼저 실행된다. 

 

비록 dispatch queue는 참조 카운트 객체이지만, retain할 필요가 없다. 그 이유는 어플리케이션에서 그들이 전역적이므로 호출을 retain하고 release한다. 그러므로, 큐들에 참조를 저장할 필요가 없다. 그들의 참조가 필요할 때마다 단지 dispatch_get_global_queue 함수를 호출하면 된다.

 

 

Creating Serial Dispatch Queues

Serial queue는 특정한 순서로 작업을 실행하길 원할 때 유용하다.

serial queue는 오직 한 번에 하나의 작업만을 실행하고, 항상 큐의 앞에서부터 작업을 밀어낸다. 

공유 자원이나 mutable data 구조를 보호하기 위해 lock 대신에 serial queue를 사용할 수 있다.

lock과 달리 serial queue는 작업이 예측가능한 순서로 실행되는 것을 보장한다.

그리고 작업을 비동기로 serial queue에 제출하는 한 큐는 데드락을 결코 걸리지 않는다.

 

concurrent queue와 다르게, 사용하기 원하는 serial queue를 만들고 관리해야 한다. 어플리케이션을 위한 serial queue를 원하는 만큼 만들 수 있지만 동시에 많은 작업을 실행할 수 있을만큼만 만들어야 한다.  만약 병렬적으로 많은 작업을 실행하길 원한다면 global concurrent queue를 사용하는 것이 맞다. serial queue를 만들었을 때, 각 큐를 위한 목적을 확인해보자. 자원 보호나 어플리케이션의 주요 행동 동기화와 같은.

 

dispatch_queue_t queue;
queue = dispatch_queue_create("com.example.MyQueue", NULL);

 

Getting Common Queues at Runtime

Grand Central Dispatch는 너의 어플리케이션으로부터 dispatch queue에 접근을 위한 기능들을 제공한다.

 

- dispatch_get_current_queue 함수

디버깅 목적이나 현재 큐를 확인하기 위함이다. block object 안에서 이 함수를 호출하면 블록이 제출된 큐를 리턴한다. 블록 밖에서 이 함수를 호출하면 디폴트 concurrent queue가 리턴된다.

 

- dispatch_get_main_queue 함수

main 스레드와 연관된 serial dispatch queue를 얻을 수 있다.

이 큐는 자동적으로 Cocoa 어플리케이션으로부터 생성되거나 dispatch_main 함수를 호출하거나 main thread 위에서 run loop를 설정할 수 있다.

 

- dispatch_get_global_queue 함수

공유 concurrent queue 중 하나를 얻는다.

 

 

Memory Management for Dispatch Queues

Dispatch queue나 다른 dispatch 객체들은 참조 카운트되는 데이터 타입이다.

serial dispatch queue를 만들 때, 초기 참조 카운트 1을 가진다.

dispatch_retain이나 dispatch_release함수는 필요한만큼 참조 카운트를 올리거나 내릴 수 있다. 참조 카운트가 0에 이르렀을 때 시스템은 비동기적으로 큐를 할당해제한다.

 

dispatch 객체를 retain, release하는 것은 그들이 사용되는 동안 메모리에 유지되는 것을 확실히 해야하므로 중요하다. 메모리 관리되는 Cocoa 객체의 경우 일반적인 률은 지나간 코드의 큐를 사용하고자 한다면 사용 전에 큐를 retain하고 더이상 필요하지 않을 때 release하는 것이다. 이 기본적인 패턴은 그것을 사용하는 한 큐가 메모리에 남아있음을 확실히 한다.

 

Note: global dispatch queue와 concurrent dispatch queue, main dispatchqueue는 retain, release할 필요가 없다. 큐를 retain, release하려는 어떤 시도도 무시된다.

 

만약 garbage-collected 어플리케이션을 실행한대도 여전히 dispatch queue와 dispatch 객체를 retain, release해야 한다. Grand Central Dispatch는 더이상 garbage collection model의 메모리 관리를 돕지 않는다.

 

 

Storing Custom Custom Context Information with a Queue

dispatch 객체들은 전부 custom context data를 연관시키는 것이 가능하다. 객체에 이 data를 set, get하기 위해서는 dispatch_set_context, dispatch_get_context를 사용하면 된다. 이 시스템은 custom data를 사용하지 않고, 할당과 재할당은 사용자에게 달려있다.

Objective-C의 pointer를 저장하거나, 큐를 식별하기 위한 자료구조를 저장하기 위해 context data를 사용할 수 있다.

 

 

Providing a Clean Up Function For a Queue

serial dispatch queue를 만든 후에, 큐를 할당 해지하기 위해 finalizer 함수를 붙일 수 있다. Dispatch queues는 참조 카운트가 되는 객체이다. 참조 카운트가 0에 다다르면 dispatchset_finalizer_f 함수를 사용할 수 있다. 이 함수는 queue와 연관된 context data를 깨끗하게 할 수 있다. 오직 context pointer가 NULL이 아닐 때 호출된다.

 

void myFinalizerFunction(void *context)
{
    MyDataContext* theData = (MyDataContext*)context;
 
    // Clean up the contents of the structure
    myCleanUpDataContextFunction(theData);
 
    // Now release the structure itself.
    free(theData);
}
 
dispatch_queue_t createMyQueue()
{
    MyDataContext*  data = (MyDataContext*) malloc(sizeof(MyDataContext));
    myInitializeDataContextFunction(data);
 
    // Create the queue and set the context data.
    dispatch_queue_t serialQueue = dispatch_queue_create("com.example.CriticalTaskQueue", NULL);
    dispatch_set_context(serialQueue, data);
    dispatch_set_finalizer_f(serialQueue, &myFinalizerFunction);
 
    return serialQueue;
}