디스패치 큐 제어하기
: GCD는 디스패치 큐에서 작업을 제어할 수 있는 많은 유용한 API를 제공한다.
dispatch_set_target_queue
: target을 세팅하기 위한 큐이다. 이것은 주로 새로 생성된 큐에 중요도를 설정하는데 사용된다.
: 시리얼과 콘커런트 디스패치 큐 모드 dispatch_queue_create함수를 사용해서 생성될 때, 스레드의 중요도는 글로벌 디스패치 큐의 기본 중요도와 같다. 생성된 이후 중요도를 수정하기 위해서는 이 함수를 사용할 수 있다.
dispatch_queue_t serialQueue = dispatch_queue_create("com.hsg2510.serial", NULL);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(serialQueue, globalQueue)
// serialQueue의 중요도는 globalQueue의 중요도와 같아진다.
// dispatch_set_target_queue함수의 첫번인자 값으로 메인 큐, 글로벌 큐를 넣으면 안된다.
: 또한 디스패치 큐의 계층 구조를 만들수도 있다. 이는 관련자료를 찾아보기 바란다.
dispatch_after
: 큐에 작업의 시작 타이밍을 설정하는 것이다. 지정된 시간 이후에 작업을 실행하려고 할 때 사용한다.
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), {
NSLog(@"hi ~~ ");
})
// dispatch_time은 첫번째 인자로 들어온 시간부터 2번째 인자의 시간만큼 지난시간 리턴한다.
// dispatch_after의 첫번째 인자는 해당 블록이 큐에 추가될 시간을 지정한다.
// 지정된 블록을 3초 후에 메인 디스패치 큐에 추가한다.
// "ull"은 C언어 문법에서 지정된 타입이다. unsigned long long
// RunLoop가 1/60초 간격으로 실행하는 경우, 블록은 3초후와 3초 + 1/60초 사이에서 실행된다. 많은 작업이 메인 디스패치 큐에 추가되거나 메인 스레드에 지연이 있으면, 그 뒤에 실행될 수 있다. 따라서 정확한 타이머 개념으로 사용하는 것은 문제지만, 대략적인 작업의 지연을 유도할 때 매우 유용하게 사용된다.
디스패치 그룹
: 디스패치 큐의 여러개의 작업들이 비동기적으로 실행될 때 모든 작업이 끝난뒤에 해야되는 작업이 있다면 디스패치 그룹을 사용하면 된다.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ NSLog(@"blk0"); });
dispatch_group_async(group, queue, ^{ NSLog(@"blk1"); });
dispatch_group_async(group, queue, ^{ NSLog(@"blk2"); });
dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"end"); });
dispatch_release(group);
//3개의 블록작업이 aync하게 동작되고 모든 작업이 끝나면 "end"가 출력된다.
또한 단순하게 디스패치 그룹과 관련된 모든 작업을 기다릴 수 있다.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORTY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ NSLog(@"blk0"); });
dispatch_group_async(group, queue, ^{ NSLog(@"blk1"); });
dispatch_group_async(group, queue, ^{ NSLog(@"blk2"); });
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
//dispatch_group_wait 함수의 두번쨰 인자값을 대기 시간을 지정하는 일시적 중단이다. FOREVER로 지정되어 있으니 그룹에 대한 작업이 끝날때까지 영원히 기다린다.
dispatch_after 함수로 배운바와 같이 1초를 기다리게 할 수 있다.
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if (result == 0)
{
// 1초 뒤에 디스패치 그룹과 연관된 모든 작업이 종료됨.
}
else
{
// 1초 뒤에도 디스패치 그룹과 연관된 작업이 모두 종료되지 않음.
}
// dispatch_group_wait 함수에 지정된 시간이 지나가는 동안 또는 그룹에 관련된 작업이 모두 종료될 때 까지 현재 스레드가 중지된다.
DISPATCH_TIME_NOW를 사용해서 현재 그룹과 연관된 작업이 모드 종료되었는지 확인하는 용도로 쓸 수도 있다.
long result = dispatch_group_wait(group, DISPATCH_TIME_NOW);
// 예를들면, 메인 스레드에서 실행되는 여러 RunLoop의 각각 루프에서 모든 작업이 끝났는지 확인하는 용도로 사용할 수 있다.
dispatch_barrier_async
: 큐에 다른 작업을 기다리는 함수이다.
: 데이터베이스 작업을 할 때, select작업은 serial로 실행될 필요가 없기 때문에 concurrent로 돌려도 된다. 하지만 도중에 update, insert작업이 들어가면 무조건 concurrent로 실행하면 안된다. 이때 유용하게 사용할 수 있는 함수이다.
dispatch_queue_t queue = dispatch_queue_create("com.hsg2510.example", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, blk0_for_reading);
dispatch_async(queue, blk1_for_reading);
dispatch_async(queue, blk2_for_reading);
dispatch_async(queue, blk3_for_reading);
dispatch_async(queue, blk4_for_reading);
dispatch_release(queue);
// 읽기 작업 5개가 비동기로 실행된다.
만약 위 5개 작업 도중 update작업이 들어가야 될 경우 곤란하다. 예를들어 첫번째, 두번째 블럭은 update 이전 데이터가 조회되야 되고 3~5번째 블럭은 update이후 데이터가 조회되야 한다면 어떻게 할것인가? 이를 위해 dispatch_barrier_async함수를 사용할 수 있다.
dispatch_async(queue, blk0_for_reading);
dispatch_async(queue, blk1_for_reading);
dispatch_barrier_async(queue, blk_for_update);
dispatch_async(queue, blk2_for_reading);
dispatch_async(queue, blk3_for_reading);
dispatch_async(queue, blk4_for_reading);
// dispatch_barrier_async함수를 위 두개의 작업이 다끝나기를 기다렸다가 그때 작업을 큐에 추가한다. 그리고 밑 3개의 작업은 update작업이 끝난뒤에 큐에 추가된다.
효과적인 데이터베이스 또는 파일 엑세스를 구현하기 위해 콘커런트 디스패치 큐와 dispatch_barrier_async함수를 사용하길 바란다.
dispatch_sync
: 디스패치 큐에 동기적으로 블록을 추가한다. 그리고 dispatch_sync가 실행된 큐는 해당 작업이 끝날때까지 멈춘다.
dispatch_queue_t_ queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{ /*task*/ });
// task가 끝날때까지 dispatch_sync를 실행한 큐는 멈춤. dispatch_sync가 호출된 뒤에 지정된 작업이 끝나기전까진 함수는 리턴을 하지 않는다.
다음 코드를 메인 스레드에서 실행할 때, 데드락이 발생할 수 있다.
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{NSLog(@"Hello?");});
//메인 큐에 블록을 추가하는데 블록이 실행되기전 블록에 대한 작업이 끝날때까지 큐를 기다리게 한다. 즉, 블록이 실행될 리가 없으므로 데드락을 일으킨다.
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
dispatch_sync(queue, ^{ NSLog(@"Hello"); });
});
//이것 역시 데드락을 일으킨다.
물론 같은 일이 시리얼 디스패치 큐에도 발생한다.
dispatch_queue_t queue = dispatch_queue_create("com.hsg2510.serialDispatchQueue", NULL);
dispatch_async(queue, ^{
dispatch_sync(queue, ^{ NSLog(@"Hello?"); });
});
// 이것 역시 데드락을 일으킨다.
*dispatch_barrier_async에 대응하는 dispatch_barrier_sync가 있다. 디스패치 큐의 모든 작업이 완료된 후 dispatch_sync함수처럼 지정된 작업이 완료되기를 기다린다.
*dispatch_sync와 같은 sync API를 사용할 때 각별히 주의해야하고, 꼭 사용해야 할때만 사용하기를 바란다.
dispatch_apply
: dispatch_sync와 dispatch_barrier_async를 합쳐놨다고 생각하면 된다.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zu", index);
});
NSLog(@"done");
//결과
//4 1 0 3 5 2 6 8 9 7 done
//첫 번째 인자값은 횟수이다. 두 번째는 타깃 디스패치 큐이고, 세 번째는 큐에 추가될 작업이고 인자로 index를 받는다.
NSArray의 각 항목에 대해 뭔가를 하기를 원할때 for-loop를 작성할 필요가 없다.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply([array count], queue, ^(size_t index) {
NSLog(@"%zu: %@", index, [array objectAtIndex:index]);
});
글로벌 디스패치 큐의 배열에 모든 항목에 대한 블록을 실행하는 것은 매우 쉽다.
dispatch_apply 함수는 dispatch_sync 함수처럼 모든 작업의 실행을 기다린다.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
//글로벌 디스패치 큐에서 dispatch_apply 함수는 모든 작업이 완료되기를 기다린다.
dispatch_apply([array count], queue, ^(size_t index) {
//NSArray 객체의 각 항목에 대해 동시에 뭔가를 한다.
NSLog(@"%zu: %@", index, [array objectAtIndex:index]);
});
// dispatch_apply 함수에 의해 모든 작업이 완료된다.
// 메인 디스패치 큐에서 비동기적으로 실행된다.
dispatch_async(dispatch_get_main_queue(), ^{
//userface 업데이트와 같은 것 등을 메인 디스패치 큐에서 실행.
});
});
dispatch_suspend/dispatch_resume
: 이 함수들은 큐의 실행을 일시 중지하거나 재개한다. 디스패치 큐에 많은 작업을 추가할 때, 때때로 그것들의 모든 것을 추가하는 것을 완료할 때까지 작업의 실행을 원하지 않을 수 있다.(블록이 다른 작업에 의해 영향을 받을 값을 캠쳐할 때 이러한 작업을 원할 수 있다)
이미 실행중인 작업들에는 영향을 주지 않는다. 디스패치 큐에는 있지만 아직 시작하지 않은 작업실행을 방지할 수 있다.
dispatch_suspend(queue);
dispatch_resume(queue);
//사용방법은 간단하다.
Dispatch Semaphore
: dispatch semaphore는 시리얼 디스패치 큐 또는 dispatch_barrier_async 함수보다 작은 부분에 대한 동시성 제어가 필요한 경우 유용하다.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i < 100000; ++i)
{
dispatch_async(queue, ^{
[array addObject:[NSNumber numberWithInt:i]];
});
}
이 소스코드에서 NSMutableArray 클래스는 멀티스레딩을 지원하지 않기 때문에 많은 스레드에서 객체가 업데이트 될 때 크래시가 발생될 수 있다. 이 문제는 전형적인 레이스 컨디션이다. 이런 상황에 디스패치 세마포어를 사용하면 된다.
흔히 멀티스레딩 프로그래밍에서 말하는 세마포어 개수를 다루는 방식과 동일하게 디스패치 세마포어도 흐름 제어를 위한 플래그 개념이다. 플래스는 계속 진행할 수 있을 때 up되고, 진행할 수 없을 때 플래그는 down된다.
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
//세마포어를 생성한다. 인자값은 카운터의 초기값이다. 이름에 "create"가 포함되어 있으므로 ARC가 아닌 환경의 경우 dispatch_release로 그것을 release해야 한다. 또한 dispatch_retaion으로 소유권을 가질수 있다.
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
//이 함수는 세마포어의 카운터가 1개 이상이 될 때 까지 기다린다. 1개 이상이 되면 카운터를 감소시키고 함수를 종료한다. 두번째 인자값은 대기시간이다. 함수 리턴값은 dispatch_group_wait함수의 값과 동일하다.
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_semaphore_wait(semaphore, time);
if (result == 0)
{
//디스패치 세마포어의 카운터는 1개 이상이다.
//또는 지정된 시간이전에 1개 이상이 된다.
// 카운터는 자동적으로 하나 감소된다.
// 여기에서 동시 제어가 필요한 작업을 실행할 수 있다.
}
else
{
//디스패치 세마포어의 카운터가 0이기 때문에 지정된 시간까지 기다린다.
}
작업이 완료되고 나서는 디스패치 세마포어의 카운터를 1개 증가시키는 dispatch_semaphore_signal 함수를 호출해야 한다.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [NSMutableArray array];
for (int i = 0; i < 100000; ++i)
{
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[array addObject:[NSNumber numberWithInt:i];
dispatch_semaphore_signal(semaphore);
});
}
dispatch_release(semaphore);
dispatch_once
: 지정된 작업이 애플리케이션 라이프타임 동안 오직 한 번 실행되는지 확인하는데 사용된다.
static int initialized = NO;
if (initialized == NO)
{
// 초기화
initialized = YES;
}
위 코드를 dispatch_once함수로 더 우아하게 구현할 수 있다.
static dispatch_once_t pred;
dispatch_once(&pred, ^{
// 초기화
});
두 소스코드는 별 차이가 없으나 멀티스레드 환경에서 첫번째 소스코드는 initialized값이 덮여쓰여지는 동시에 읽힐 수도 있는 약간의 기회가 있어서 100프로 안전하지 않다. 그러나 dispatch_once 함수로 구현을 하면 더는 걱정할 필요가 없다.
'IOS > Objective-C' 카테고리의 다른 글
Block의 모든것(1) (0) | 2017.01.02 |
---|---|
ARC 규칙 (0) | 2017.01.01 |
GCD(Grand Central Dispatch)(1) (0) | 2016.12.19 |
Objective-C의 동적 바인딩, 그리고 Message dispather와 Runtime (0) | 2016.12.18 |
Key-Value Coding(1) (0) | 2016.12.07 |