본문 바로가기

프로그래밍/iOS

GCD(Grand Central Dispatch) 사용

  1. GCD : Grand Central Dispatch
    - iOS 4부터 지원하는 스레드 관리 기술
    - 블록 코딩 기반으로 NSThread, NSOperation보다 쉽게 사용 가능


  2. GCD Queue
    - 실행할 작업을 저장하는 큐로 작업을 정의하고 큐에 넣으면 나머지는 OS에서 알아서 처리
    - 블록의 경량화 된 리스트라고 생각하면 됨

    - Dispatch Queue
       1) 시리얼 디스패치 큐(Serial Dispatch Queue)
          * 한 스레드에서 하나의 작업 씩 순차처리(FIFO)하며 처리 외 작업들은 대기
          * 호출 할 때 마다 스레드가 추가되므로 과도하게 호출 할 경우 성능에 문제가 생길 수 있음

       2) 컨커런트 디스패치 큐(Concurrent Dispatch Queue)
          * 작업들을 순서와 상관없이 별도의 스레드에서 실행
          * 동시에 실행되는 작업 수는 시스템 상태에 따라 다름(XNU 커널이 알아서 판단 & 관리)

    - 직렬 큐
       1) 추가된 작업을 순서대로 실행하는 큐(Serial Dispatch Queue)

    - 병렬 큐(Global Queue)
       1) 추가된 작업을 병렬로 실행하는 큐(Concurrent Dispatch Queue)
       2) 기본적으로 4개의 큐가 존재하여 중요도에 따라 HIGH, DEFAULT, LOW, BACKGROUND로 나눠져 실행

    - 메인 큐(Main Queue)
       1) 추가된 작업을 메인 스레드에서 실행하는 큐(Serial Dispatch Queue)
       2) UI작업은 메인큐에서 처리해야 함

  3. // 시리얼 디스패치 큐 생성
    dispatch_queue_t serialQueue1 = dispatch_queue_create("serialQueue", NULL);
    dispatch_queue_t serialQueue2 = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
        
    // 컨커런트 디스패치 큐 생성
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
        
    // 글로벌 큐 생성
    dispatch_queue_t globalQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    dispatch_queue_t globalQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t globalQueue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    dispatch_queue_t globalQueue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
        
    // 메인 큐 생성
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    


  4. 큐에 작업 추가
    - dispatch_async: 비동기로 실행되는 작업 추가
    - dispatch_sync: 순차적으로 실행되는 작업 추가

    - 실행순서 관련 코드 및 실행 결과
    // 1.serial dispatch queue + sync
    dispatch_queue_t serialQueue1 = dispatch_queue_create("serialQueue", NULL);
    dispatch_sync(serialQueue1, ^{NSLog(@"working task 1");});
    dispatch_sync(serialQueue1, ^{NSLog(@"working task 2");});
    dispatch_sync(serialQueue1, ^{NSLog(@"working task 3");});
    dispatch_sync(serialQueue1, ^{NSLog(@"working task 4");});
    dispatch_sync(serialQueue1, ^{NSLog(@"working task 5");});
    
    // 2.serial dispatch queue + async
    dispatch_queue_t serialQueue2 = dispatch_queue_create("serialQueue", NULL);
    dispatch_async(serialQueue2, ^{NSLog(@"working task 1");});
    dispatch_async(serialQueue2, ^{NSLog(@"working task 2");});
    dispatch_async(serialQueue2, ^{NSLog(@"working task 3");});
    dispatch_async(serialQueue2, ^{NSLog(@"working task 4");});
    dispatch_async(serialQueue2, ^{NSLog(@"working task 5");});
    
    // 3.concurrent dispatch queue + sync
    dispatch_queue_t concurrentQueue1 = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(concurrentQueue1, ^{NSLog(@"working task 1");});
    dispatch_sync(concurrentQueue1, ^{NSLog(@"working task 2");});
    dispatch_sync(concurrentQueue1, ^{NSLog(@"working task 3");});
    dispatch_sync(concurrentQueue1, ^{NSLog(@"working task 4");});
    dispatch_sync(concurrentQueue1, ^{NSLog(@"working task 5");});
        
    // 1번, 2번, 3번 출력 결과: 순차 실행
    //working task 1
    //working task 2
    //working task 3
    //working task 4
    //working task 5
        
    // 4. concurrent dispatch queue + async
    dispatch_queue_t concurrentQueue2 = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentQueue2, ^{NSLog(@"working task 1");});
    dispatch_async(concurrentQueue2, ^{NSLog(@"working task 2");});
    dispatch_async(concurrentQueue2, ^{NSLog(@"working task 3");});
    dispatch_async(concurrentQueue2, ^{NSLog(@"working task 4");});
    dispatch_async(concurrentQueue2, ^{NSLog(@"working task 5");});
       
    // 4번 출력 결과: 비동기 실행
    //working task 3
    //working task 1
    //working task 2
    //working task 4
    //working task 5
    


    - retainCount 관련 코드 및 실행 결과

    // 1. serial dispatch queue + sync
    dispatch_queue_t t_queue1 = dispatch_queue_create("test.queue1", NULL);
    NSLog(@"before Working : %lu", (unsigned long)t_queue1.retainCount);
    
    dispatch_sync(t_queue1, ^{NSLog(@"working task 1 : %lu", (unsigned long)t_queue1.retainCount);});
    dispatch_sync(t_queue1, ^{NSLog(@"working task 2 : %lu", (unsigned long)t_queue1.retainCount);});
    dispatch_sync(t_queue1, ^{NSLog(@"working task 3 : %lu", (unsigned long)t_queue1.retainCount);});
    dispatch_sync(t_queue1, ^{NSLog(@"working task 4 : %lu", (unsigned long)t_queue1.retainCount);});
    dispatch_sync(t_queue1, ^{NSLog(@"working task 5 : %lu", (unsigned long)t_queue1.retainCount);});
    
    NSLog(@"after all Working : %lu \n\n", (unsigned long)t_queue1.retainCount);
    dispatch_release(t_queue1);
    
    // 2. concurrent dispatch queue + sync
    dispatch_queue_t t_queue2 = dispatch_queue_create("test.queue2", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"before Working : %lu", (unsigned long)t_queue2.retainCount);
    
    dispatch_sync(t_queue2, ^{NSLog(@"working task 1 : %lu", (unsigned long)t_queue2.retainCount);});
    dispatch_sync(t_queue2, ^{NSLog(@"working task 2 : %lu", (unsigned long)t_queue2.retainCount);});
    dispatch_sync(t_queue2, ^{NSLog(@"working task 3 : %lu", (unsigned long)t_queue2.retainCount);});
    dispatch_sync(t_queue2, ^{NSLog(@"working task 4 : %lu", (unsigned long)t_queue2.retainCount);});
    dispatch_sync(t_queue2, ^{NSLog(@"working task 5 : %lu", (unsigned long)t_queue2.retainCount);});
    
    NSLog(@"after all Working : %lu \n\n", (unsigned long)t_queue2.retainCount);
    dispatch_release(t_queue2);
    
    /* 1번, 2번 출력 결과: retainCount가 증가하지 않음
    before Working : 1
    working task 1 : 1
    working task 2 : 1
    working task 3 : 1
    working task 4 : 1
    working task 5 : 1
    after all Working : 1 */
    
    
    // 3. serial dispatch queue + async
    dispatch_queue_t t_queue3 = dispatch_queue_create("test.queue3", NULL);
    NSLog(@"before Working : %lu", (unsigned long)t_queue3.retainCount);
    
    dispatch_async(t_queue3, ^{NSLog(@"working task 1 : %lu", (unsigned long)t_queue3.retainCount);});
    dispatch_async(t_queue3, ^{NSLog(@"working task 2 : %lu", (unsigned long)t_queue3.retainCount);});
    dispatch_async(t_queue3, ^{NSLog(@"working task 3 : %lu", (unsigned long)t_queue3.retainCount);});
    dispatch_async(t_queue3, ^{NSLog(@"working task 4 : %lu", (unsigned long)t_queue3.retainCount);});
    dispatch_async(t_queue3, ^{NSLog(@"working task 5 : %lu", (unsigned long)t_queue3.retainCount);});
    
    dispatch_release(t_queue3);
    NSLog(@"after Working : %lu \n\n", (unsigned long)t_queue3.retainCount);
    
    /* 3번 출력 결과: 작업 수에 따라 retainCount가 증가하지만 블록이 끝나면 감수
    before Working : 1
    after all Working : 5
    
    working task 1 : 5
    working task 2 : 4
    working task 3 : 3
    working task 4 : 2
    working task 5 : 1 */
    
    
    // 4. concurrent dispatch queue + async
    dispatch_queue_t t_queue4 = dispatch_queue_create("test.queue4", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"before Working : %lu", (unsigned long)t_queue2.retainCount);
    
    dispatch_async(t_queue4, ^{NSLog(@"working task 1 : %lu", (unsigned long)t_queue4.retainCount);});
    dispatch_async(t_queue4, ^{NSLog(@"working task 2 : %lu", (unsigned long)t_queue4.retainCount);});
    dispatch_async(t_queue4, ^{NSLog(@"working task 3 : %lu", (unsigned long)t_queue4.retainCount);});
    dispatch_async(t_queue4, ^{NSLog(@"working task 4 : %lu", (unsigned long)t_queue4.retainCount);});
    dispatch_async(t_queue4, ^{NSLog(@"working task 5 : %lu", (unsigned long)t_queue4.retainCount);});
    
    dispatch_release(t_queue4);
    NSLog(@"after Working : %lu \n\n", (unsigned long)t_queue4.retainCount);
    
    /* 4번 출력 결과: 작업 수에 따라 retainCount가 증가하지만 블록이 끝나면 감수
     before Working : 1
     after all Working : 5
     
     working task 1 : 5
     working task 2 : 5
     working task 3 : 5
     working task 4 : 5
     working task 5 : 5 */
    


  5. dispatch_group
     - 작업을 그룹단위로 묶어서 관리

  6. // globla queue 생성
    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, ^{ /* 이 곳에 작업을 추가해 주세용 */ });
    
    
    // 그룹에 추가한 작업이 완료될 때 까지 대기. 작업이 완료 시 실행할 작업 큐에 추가
    // 해당 메소드 호출 이후에 비동기로 추가된 작업까지 포함하여 기다림
    dispatch_group_notify(group, queue, ^{ /* 이 곳에 작업을 추가해 주세용 */ });
    
    
    // 지정한 시간만큼 또는 그룹에 작업이 완료될 때 까지 기다림
    // 스레드를 정지시키므로 백그라운드에서 실행해야 함
    // return 값 (작업 완료 후 리턴: 0, 작업이 남아있는 경우: 그 외의 값)
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // 그룹에 작업이 완료되면 리턴 0
    dispatch_group_wait(group, dispatch_time_t timeout); // 정해진 시간이 지나면 리턴 !0
    
    
    // 그룹 안에 새로운 작업의 시작과 끝을 알림.
    dispatch_group_enter(group); // 작업의 시작.
    /*
     큐에서 처리하는 작업 외의 작업을 입력.
     여기 들어가는 작업은 하나의 작업으로 인식됨.
     */
    dispatch_group_leave(group); // 작업의 끝.
    


  7. dispatch_barrier
    - 동시에 실행되서는 안되는 작업을 sync/async하게 추가(이 작업 외에 다른 작업들은 대기)
    - race condition(하나의 자원 동시 사용) 방지
    - 작업이 너무 클 경우 기다리는 시간이 길어져 비효율

  8.  
    // 동시에 실행하면 안되는 작업 추가
    dispatch_barrier_async(queue, ^{ /* 이 곳에 작업을 추가해 주세용 */ }); // 비동기
    dispatch_barrier_sync(queue, ^{ /* 이 곳에 작업을 추가해 주세용 */ }); // 동기
    
    
    // 예제
    dispatch_queue_t queue = dispatch_queue_create("com.testqueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{ NSLog(@"Call 1"); });
    dispatch_async(queue, ^{ NSLog(@"Call 2"); });
    dispatch_async(queue, ^{ NSLog(@"Call 3"); });
    dispatch_barrier_sync(queue, ^{ NSLog(@"Call 4-1");});
    dispatch_barrier_sync(queue, ^{ NSLog(@"Call 4-2");});
    dispatch_barrier_sync(queue, ^{ NSLog(@"Call 4-4");});
    dispatch_barrier_sync(queue, ^{ NSLog(@"Call 4-3");});
    dispatch_barrier_sync(queue, ^{ NSLog(@"Call 4-5");});
    dispatch_async(queue, ^{ NSLog(@"Call 8"); });
    dispatch_async(queue, ^{ NSLog(@"Call 9"); });
    dispatch_async(queue, ^{ NSLog(@"Call 10"); });
    
    /* 결과
     Call 1
     Call 3
     Call 2
     Call 4-1
     Call 4-2
     Call 4-3
     Call 4-4
     Call 4-5
     Call 8
     Call 9
     Call 10 */
    // barrier로 추가된 작업이 진행되는 동안 다른 작업은 대기.
    // async로 추가될 경우 (4-1)~(4-5) 작업 순서가 보장되지 않는 것만 다름
    


  9. dispatch_semaphore
    - 한 작업 단위보다 작은 부분에서 대기가 필요한 경우(ex: 이미지 로딩 완료 후 UI에 적용)
    - wait와 signal의 호출 횟수는 같아야 함(문제가 생길 수 있음)
    - MRC에서는 별도 메모리 관리 필요

    // value값을 count로 가지는 semaphore 생성.
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(long value);
    
    // 호출 시 count값 -1(count가 0 미만이 되는 경우 signal 호출까지 기다림)
    // 데드락 방지를 위해 시간 설정 가능.
    // return 값 (작업 완료 후 리턴: 0, 타임아웃: 그 외의 값)
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 계속 기다림
    dispatch_semaphore_wait(semaphore, dispatch_time_t timeout); // 시간 지나면 타임아웃
    
    // 호출 시 count +1
    dispatch_semaphore_signal(semaphore);
    
    
    //예제
    dispatch_queue_t queue = dispatch_queue_create("com.testqueue", NULL);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    dispatch_async(queue, ^{
        sleep(100); // 바로 실행되지 않게 하기위해 sleep을 걸어 줌
        NSLog(@"text1");
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"Done1");
    
    /* 결과
     text1
     Done1 */
    // wait 호출과 동시에 count값이 -1이 되고 signal을 기다림.
    // text1을 출력하고 signal을 만나 count값이 0이되고 Done을 출력.