먼저 기본적인 GCD의 기능에 대해서 알아보자.


GCD는 태스크를 저장하는 큐(디스패치큐)를 이용하여 동작하며 3종류의 큐가 정의 되어 있다.

첫번째 글에도 잠깐 언급하였지만 간단히 다시 한번 설명하자면,


직렬큐:태스크를 추가한순으로 실행되는 큐

dispatch_queue_create("jp.hateblo.shin.LABEL_NAME", NULL)


병렬큐:추가된태스크를 병렬로 실행하는 큐 (4개의 글로벌 병렬큐가 이미 준비되어있다.)

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)


메인큐:추가된태스크가 메인스레드에서 실행되는큐. ( UI작업은 이큐에서 동작해야함.)

큐의 취득은 dispatch_get_main_queue()


각각의 큐는 코드도 서로 다르지만 사용하는방법은 같다.


태스크에 등록할 때에는dispatch_async 나 dispatch_sync의 두가지 방법이 있다. 태스크의 등록할 시점에 동기로 실행할지 비동기일지를 지정한다.


직렬큐라면..

#import <Foundation/Foundation.h>


int main(int argc, char* argv[])

{

    @autoreleasepool {

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        dispatch_async(queue, ^{

            [NSThread sleepForTimeInterval:1];

            NSLog(@"Done: %d", 1);

        });

        dispatch_async(queue, ^{

            [NSThread sleepForTimeInterval:1];

            NSLog(@"Done: %d", 2);

        });

        // 여기서 queue 쌓인 태스크가 전부 끝날때까지 메인스레드는 대기한다.

        dispatch_sync(queue, ^{

            [NSThread sleepForTimeInterval:1];

            NSLog(@"Done: %d", 3);

        });

    }

    return 0;

}


실행 결과출력.

2015-03-17 16:05:50.864 TestApp3[5693:1456773] Done: 3

2015-03-17 16:05:50.864 TestApp3[5693:1457020] Done: 1

2015-03-17 16:05:50.864 TestApp3[5693:1457019] Done: 2




병렬큐를 구현한다면..

#import <Foundation/Foundation.h>


int main(int argc, char* argv[])

{

    @autoreleasepool {

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        dispatch_async(queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"@Done: %d", 1);

        });

        dispatch_async(queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"@Done: %d", 2);

        });

        dispatch_sync(queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"@Done: %d", 3);

        });

        // 여기서 queue 병렬처리 하고 샆다면..

    }

    return 0;

}


dispatch_sync를 사용함으로써 큐에 쌓인 태스크가 종료될때까지 대기하므로 메인스레드에서 별도의 처리를 할 수 없게된다.

이런경우에는 디스패치 그룹을 사용하면 된다.


#import <Foundation/Foundation.h>


int main(int argc, char* argv[])

{

    @autoreleasepool {

        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, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"Done: %d", 1);

        });

        dispatch_group_async(group, queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"Done: %d", 2);

        });

        dispatch_group_async(group, queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"Done: %d", 3);

        });

        

        // 이지점에서 쌓인큐를 종료됨과 동시에 호출된다.

        // dispatch_group_wait 포함 되지 않는다.

        dispatch_group_notify(group, queue, ^{

            NSLog(@"All tasks are done!");

        });

        

        NSLog(@"do something...");

        [NSThread sleepForTimeInterval:1];

        NSLog(@"waiting...");

        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

        // 여기서 약간 기다려주지 안흥면 notify처리전에 프로그램의 끝나버리므로..약간 딜레이를

        [NSThread sleepForTimeInterval:1];

    }

    return 0;

}



2015-03-17 16:06:22.744 TestApp3[5717:1461092] do something...

2015-03-17 16:06:23.749 TestApp3[5717:1461092] waiting...

2015-03-17 16:06:25.744 TestApp3[5717:1461228] Done: 3

2015-03-17 16:06:25.744 TestApp3[5717:1461229] Done: 2

2015-03-17 16:06:25.744 TestApp3[5717:1461232] Done: 1

2015-03-17 16:06:25.744 TestApp3[5717:1461232] All tasks are done!





dispatch_group_async를 태스크에 등록을 하면 필요할 경우에 dispatch_group_wait로 큐에 쌓인 태스크가 종료되는것을 기다릴 수 있다.

dispatch_group_notify은 옵션이지만 종료처리에 이용할 수 있다.


디스패치와 세마포어


GCD는 세마포어를 이용할 수도 있다. (세마포어란 전글에도 설명했듯이 배타제어를 위한 방법중 하나이다.)

물론 직렬큐를 잘만 이용한다면 크리티컬섹션을 이용한 세마포어가 필요없겠지만 어쨋든 직력큐와 같은 효과를 낼 수 있다.


#import <Foundation/Foundation.h>


int main(int argc, char* argv[])

{

    @autoreleasepool {

        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); //열쇠갯수

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        

        dispatch_async(queue, ^{

            [NSThread sleepForTimeInterval:2]; // do something

            NSLog(@"Finish!");

            dispatch_semaphore_signal(semaphore); //열쇠갯수 늘림

        });

        

        [NSThread sleepForTimeInterval:1]; // do something

        NSLog(@"waiting...");

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); //열쇠반환될때 까지 기다림

        

    }

    return 0;

}





2015-03-17 16:06:59.677 TestApp3[5746:1465642] waiting...

2015-03-17 16:07:00.677 TestApp3[5746:1465821] Finish!



GCD에 대해선 언급한 것 이외에도 너무나도 많은 기능들이 있으니 애플문서를 참고하면 될듯하다. 











Posted by 악당잰 트랙백 0 : 댓글 4

댓글을 달아 주세요

  1. addr | edit/del | reply BlogIcon 魔術士マジク 2015.03.20 23:47

    검은건 글씨요 흰건 바탕페이지라....ㅠㅠ

    • addr | edit/del 악당잰 2015.03.21 12:56

      에이 ~ 다 알면서~

  2. addr | edit/del | reply 김기봉 2015.03.24 14:46

    병렬큐에서
    dispatch_async(queue, ^{
    [NSThread sleepForTimeInterval:3];
    NSLog(@"@Done: %d", 2);
    });
    dispatch_sync(queue, ^{
    [NSThread sleepForTimeInterval:3];
    NSLog(@"@Done: %d", 3);
    });

    에서 dispatch_sync랑 dispatch_async를 다르게 준건 무슨 의도로 주신거예요?

  3. addr | edit/del | reply BlogIcon 악당잰 2015.03.24 17:30 신고

    마지막에 싱크를 안해주면 메소드가 끝나버리니까 확인이 안되잖어. 그래서 동기로 3초준거지

저번 스레드글에 이어서 NSOperation에 대해서 조금 덧붙여 이야기 해보고려고 한다. 자세한 내용은 애플 개발자문서에 너무도 자세히 나와 있으므로 여기서는 그냥 이렇게 움직인다라는 설명정도만 하려고 한다.




Cocoa에서는 스레드 병렬처리를 위한 고수준 인터페이스를 제공하고 있다. 뭐냐하면 바로  GCD와NSOperation/NSOperationQueue이 두가지 이다.

이 두가지 방법으로 멀티스레드처리가 가능한데 이 방법의 장점은 직렬큐를 이용하거나(GCD의 경우) 큐의 태스크처리를 지정하는 방법등으로 간단히 골치아픈 데드락을 피할 수 있다. ( 다만 리얼타임처리 요하는 경우에는 스레드를 이용하는편이 낫다. 아주 빠른속도의 처리를 위한 특수한 경우가 해당될듯 하다.)





NSOperation/NSOperationQueue은 내부적으로는 GCD로 구현되어 있다고 한다. 결론적으로 NSOperation/NSOperationQueue는 GCD의Objective-C랩퍼 클래스 인 것이다.

GCD에 비교해서NSOperation/NSOperationQueue가 고수준처리가 가능하다는것은 이전 글에서도 잠깐 언급했지만 얻는만큼 잃는것도 있는법. 그대신 그만큼 오버해드가 발생한다고 한다. 오버해드가 발생할만한 코드를 짜본 적이 없으므로 아직은 잘 모르겠다.

아무튼 그런점의 유념해서 개발시에 GCD를 사용할 것인지를 판단해야 할것이다.


그럼 먼저 NSOperation/NSOperationQueue 에 대해서 설명을 하자면,


NSOperation/NSOperationQueue의 사용방법



NSOperation/NSOperationQueue의 기본적인 개념은 태스크(NSOperation)를 큐(NSOperationQueue)에 추가하여 실행하는 방식으로 설계되어 있다.

코드를 보는편이 이해가 빠를지도..아래의 코드를 보자.


#import <Foundation/Foundation.h>


NSBlockOperation* createAnOperation()

{

    // NSBlockOperation 복수의 블록을 가질 있고 그것을 병렬로 실행 시킬수 있다.

    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock: ^{

        [NSThread sleepForTimeInterval:1];

        NSLog(@"Done: 1");

    }];

    [op addExecutionBlock: ^{

        [NSThread sleepForTimeInterval:1];

        NSLog(@"Done: 2");

    }];

    [op addExecutionBlock: ^{

        [NSThread sleepForTimeInterval:1];

        NSLog(@"Done: 3");

    }];

    return op;

}


int main(int argc, char* argv[])

{

    @autoreleasepool {

        NSArray *ops = @[createAnOperation(), createAnOperation()];

        NSOperationQueue *queue = [[NSOperationQueue allocinit];

        // 여기서 오퍼레이션을 직렬로 실행하기 위해서 오퍼레이션 동시실행수를 1 설정

        [queue setMaxConcurrentOperationCount:1];

        // waitUntilFinishedYES 하면 대기한다.

        [queue addOperations:ops waitUntilFinished:NO];

        // 큐에 직접 블록오퍼레이션을 추가하는 것도 가능하다.

        [queue addOperationWithBlock: ^{

            NSLog(@"All done!");

        }];

        // 전부 오퍼레이션이 끝날때까지 대기.

        [queue waitUntilAllOperationsAreFinished];

    }

    return 0;

}


결과는 아래와 같이 출력된다.

2015-03-17 16:04:47.026 TestApp3[5652:1449633] Done: 1

2015-03-17 16:04:47.026 TestApp3[5652:1449632] Done: 2

2015-03-17 16:04:47.026 TestApp3[5652:1449638] Done: 3

2015-03-17 16:04:48.032 TestApp3[5652:1449632] Done: 3

2015-03-17 16:04:48.032 TestApp3[5652:1449638] Done: 1

2015-03-17 16:04:48.032 TestApp3[5652:1449633] Done: 2

2015-03-17 16:04:48.032 TestApp3[5652:1449632] All done!




NSBlockOperation는 표준으로 준비되어 있는 NSOperation의 자식클래스이다. 설명에는 NSBlockOperation만 예를 들었지만 NSInvocationOperation이라는 Target, Selector를 지정해 줄 수 있는 타입도 있다. 또한 NSOperation를 상속받아 커스텀할 수 있도 있다. 더 알고싶다면 애플 개발자 문서를 참조하자.


NSOperationQueue은 기본적으로 GCD라는 병렬큐이다. 위의 예에서는 setMaxConcurrentOperationCount:1로 설정함으로써 직렬큐처럼(태스크가 하나만 처리되므로 결과적으로는 직렬큐) 사용할 수도 있다. 그와 반대로 복수개의 오퍼레이션을  추가하여 이것을 병렬로도 실행 할 수 있다. 


하나 더 유용한 기능이 있는데 NSOperation은 오퍼레이션간에 의존관계를 설정하는 것도 가능하다. 위의 코드는 직렬관계의 오퍼레이션을 동시에 실행하는수를 제한했지만 아래의 코드는 의존관계를 이용하여 실행하는것도 가능하다.


#import <Foundation/Foundation.h>


int main(int argc, char* argv[])

{

    @autoreleasepool {

        NSOperation *op1 = [NSBlockOperation blockOperationWithBlock: ^{

            [NSThread sleepForTimeInterval:1];

            NSLog(@"Done: 1");

        }];

        NSOperation *op2 = [NSBlockOperation blockOperationWithBlock: ^{

            [NSThread sleepForTimeInterval:1];

            NSLog(@"Done: 2");

        }];

        [op2 addDependency: op1]; // op2op1 의존

        NSOperationQueue *queue = [[NSOperationQueue alloc] init];

        [queue addOperations:@[op1, op2] waitUntilFinished:NO];

        [queue waitUntilAllOperationsAreFinished];

    }

    return 0;

}


여기서는 설명하지 못했지만 각각의 오퍼레이션의 우선도를 설정한다던지, 완료시에 콜백으로 불려지는등등 블록을 설정하는것도 가능하다. 실제로 이 클래스를 사용해보면 NSThread를 이용하는것보다 훨씬 간편하다는것을 알수 있다.

게다가 멀티스레드에서 제일 골치아픈 배타적제어 문제도 직렬큐를 이용하면 간단히 해결할 수 있으므로 일석이조라고 할 수 있겠다.




Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요