이 블로그 검색

2011년 3월 25일 금요일

Intro to Grand Central Dispatch, Part III: Dispatch Sources



Dispatch Sources

이벤트를 모니터 하기 위한 객체.
이벤트 발생시, 자동으로 dispatch queue상의 특정 block을 실행한다.

이벤트 종류는 다음과 같다(GCD in 10.6.0).

1.Mach port send right state changes.
2.Mach port receive right state changes.
3.External process state change.
4.File descriptor ready for read.
5.File descriptor ready for write.
6.Filesystem node event.
7.POSIX signal.
8.Custom timer.
9.Custom event.
1. Custom event
   dispatch_source_merge_data 함수를 호출하여 자신에게 시그널처리하는 경우 발생한다.
    2가지 종류가 존재 한다.
DISPATCH_SOURCE_TYPE_DATA_ADD
DISPATCH_SOURCE_TYPE_DATA_OR

Custom event 처리 예:
어떠한 작업들의 완료로 진행바를 업데이트하는 작업을 예로 들어보자.

- source 객체를 생성하고(dispatch_source_create),

- 각각의 작업 완료시 호출될 이벤트 핸들러(block/function)을  설정한다
  (dispatch_source_set_event_handler).
  완료 이벤트를 받으면 source객체는 등록된 핸들러(block/function)를  실행해서 진행바를
  업데이트 한다. 이 GUI 업데이트 작업은 main queue에서 처리되어야 하므로 source생성시
  dispatch_get_main_queue를 호출했다.

- 프로그레스바 업데이트를 위한 값을 구하는 것은 글로벌 큐에서 수행된다.
   하나의 작업이 완료될때마다 dispatch_source_merge_data를 호출하고 값으로 1을 전달한다.    
   dispatch_source_merge_data 호출을  하면 source객체의 event handler가 호출된다.
   이벤트 핸들러 에서는 dispatch_source_get_data 함수로 값을 가져올수있다
   (호출하면 값은 0으로 reset됨).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
dispatch_source_t source
  =  dispatch_source_create
  ( DISPATCH_SOURCE_TYPE_DATA_ADD,
   0,
   0,
   dispatch_get_main_queue());
   
 dispatch_source_set_event_handler(source,
     ^{
         [progressIndicator incrementBy: dispatch_source_get_data(source)];
     });      
//dispatch source는 항상 suspended 상태로 시작되기때문에
//생성후 이벤트 처리를 윈하면 resume시켜야한다.
dispatch_resume(source);
    
dispatch_apply([array count], globalQueue,
     ^(size_t index) {         
         // 이부분에서 인덱스로 구한 데이터에 대한 처리 수행
         // .....
         
         //하나의 작업 완료후 이벤트 발생.
         dispatch_source_merge_data(source, 1);
     } );
Pastie #2500389 linked directly from Pastie.
 
dispatch_source_merge_data 호출을 하면 source객체의 event handler가 호출된다.
만약 여러번의 호출이 발생하면 event handler도 여러번 발생할것이다.

그런데 다음 상황을 고려해보자.
GUI 업데이트 작업은 메인 큐에서 수행되는데 만약 메인큐를 처리하는 메인 쓰레드가 다른 작업등으로 인해
바쁜 경우, 메인큐의 작업은 지연되고있을 것이다.
이때 DISPATCH_SOURCE_TYPE_DATA_ADD 타잎의 source 객체인 경우면, 처리되지 못하고
대기중인 여러 이벤트들은 하나로 통합(coalesce) 된다.

그리고 핸들러에서 사용될 값(dispatch_source_merge_data 호출시 넘기는 unsigned long 값)은 dispatch_source_merge_data 호출 할때 마다 계속 더해진다.

이후 메인 쓰레드가 처리가능시점이 되면 event handler가 한번 호출되면서 그동안 누적된 값이
전달된다. dispatch_source_get_data 호출을 하면 값은 다시 0으로 초기화된다.

즉, 이벤트가 바로바로 처리 안되는 경우, 이벤트를 계속 누적시키지 않고 처리가능
시점에 한번만 처리할수 있게 이벤트를 통합하면서 데이터값은 계속 누적시켜 주는 것이다.

하지만 메인 쓰레드가 여유가 있어서 메인 큐에서 바로 바로 가져와 처리 가능하다면,
이벤트 핸들러는 통합처리없이 그때그때마다 수행될것이다.

이벤트 통합을 원하지 않는다면 dispatch source를 사용할 필요는 없다. dispatch_async 호출을 하면 된다.

dispatch_async 대신 dispatch source를 사용하는 오직 하나의 이유라면 바로 이벤트 통합처리 때문이다.

2. Built-In Events  

기본으로 제공되는 이벤트는 어떤것이 있는지 알아본다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
DISPATCH_SOURCE_TYPE_READ : file descriptor를 모니터한다.
 
 dispatch_queue_t globalQueue  =  dispatch_get_global_queue
         (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_source_t stdinSource
      = dispatch_source_create  ( DISPATCH_SOURCE_TYPE_READ,
        STDIN_FILENO, //file descriptors
               0,
        globalQueue );
                                
    dispatch_source_set_event_handler ( stdinSource,
     ^{
      char buf[1024];
         int len = read(STDIN_FILENO, buf, sizeof(buf));
         if(len > 0)
             NSLog(@"Got data from stdin: %.*s", len, buf);
     });
     
    dispatch_resume(stdinSource);
Pastie #2500392 linked directly from Pastie.
    
주의할점은 일기 작업이 완료되었다고 해도 dispatch source가 살아있다면 filer 기술자를 close하면 안된다.
만약 다른 source 객체에서 동일한 파일기술자 번호로 생성이 된다면, 그 source 객체는 원하지 않는
읽기가 발생할것이다.

이경우 dispatch_source_set_cancel_handler 함수를 이용한다.
dispatch_source_set_cancel_handler 호출하면서 파일기술자를 닫는 핸들러(block)를 인자로 주고,
그후 dispatch_source_cancel 함수를 호출하여 cancel 핸들러가 호출되게 할수있다.
그럼 파일 기술자가 close될것이다.

좀더 자세한 사항은 apple man 페이지를 참조한다.

DISPATCH_SOURCE_TYPE_TIMER : 주기적으로 이벤트를 발생시킨다.

이 타잎은 좀 특별하다. handle/mask 인자없이 생성되며, 별도의 dispatch_source_set_timer
함수를 이용해서 타이머를 설정한다.

void dispatch_source_set_timer (  dispatch_source_t source,
                              dispatch_time_t start,
                              uint64_t interval,
                                  uint64_t leeway );

leeway:  타이머 정확성을 지정한다. 0 이라면 지정된 interval 을 최대한 정확하게 지켜달라는 의미.
그외 지정되는 숫자는 그 만큼의 interval  증가도 무방하다는 의미이다.
성능향상 및 파워 소비량을 감소 목적.

온전한 내용을 원하신다면 원문을 참조하기 바랍니다...

Intro to Grand Central Dispatch, Part II: Multi-Core Performance 요약



Grand Central Dispatch, Part II: Multi-Core Performance 요약
dispatch 큐는 worker 쓰레드풀을 추상화 한다

GCD를 통해서 멀티코어 퍼포먼스를 뽑아낼수있는 2가지 방법

1.하나의 태스크를 병행처리

2.연관된 여러 작업들을 하나의 글로벌큐에, 관련없거나
느슨하게 관련된 작업들은 여러개의 커스텀 큐를 이용해 병행처리.

1
2
for(id obj in array)
 [self doSomethingIntensiveWith:obj];
Pastie #2500401 linked directly from Pastie.

doSomethingIntensiveWith 가 병행처리 가능한 상황이면 GCD를 이용해서
다음처럼 변경 할수있다.

1
2
3
4
5
6
dispatch_queue_t queue
 =  dispatch_get_global_queue
  (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

for(id obj in array)
    dispatch_async(queue, ^{[self doSomethingIntensiveWith:obj]; });
Pastie #2500404 linked directly from Pastie.

그런데 만약 하나의 처리결과를 다른것이 참조하는
다음과 같은 상황이면

1
2
3
4
5
for(id obj in array){
 [self doSomethingIntensiveWith:obj];
}

[self doSomethingWith:array];
Pastie #2500408 linked directly from Pastie.
위에서의 dispatch_async 은 사용할수없다.
이문제를 해결하기 위한 방법중 하나는 dispatch groups을 사용하는것이다.
dispatch groups은 여러개의 block들을 하나의 그룹으로 만들고,
block들이 완료 되기를 기다리거나 완료시 통보를 받기 위한 방법이다.

사용법
1. dispatch_group_create 를 이용 생성
2. dispatch_group_async 를 이용, block을 dispatch하고 동시에 그룹에 추가한다.

그룹을 이용해서 위 코드를 변경하면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
dispatch_queue_t queue
  = dispatch_get_global_qeueue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
dispatch_group_t group = dispatch_group_create();

for(id obj in array)
  dispatch_group_async(group, queue, ^{[self doSomethingIntensiveWith:obj];});
    
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
    
[self doSomethingWith:array];
Pastie #2500414 linked directly from Pastie.

만약 이 작업이 비동기적으로 수행되어도 된다면 dispatch_group_async 를 이용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dispatch_queue_t queue
  = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_group_t group = dispatch_group_create();

for(id obj in array)
{
  dispatch_group_async(group, queue,
    ^{ [self doSomethingIntensiveWith:obj]; });
}

//그룹이 완료되면 호출될 block을 지정
dispatch_group_notify(group, queue, ^{ [self doSomethingWith:array]; });

dispatch_release(group);
Pastie #2500419 linked directly from Pastie.
최종 결과를 가지고 처리해야 할 작업(doSomethingWith)도 백그라운로 처리된다.

만약 doSomethingWith이 main thread에서 처리되어야 한다면 (GUI변경등),
dispatch_group_notify에 global queue가 아닌 main queue 를 전달하면 된다.

GCD에서는 동기적() 경우를 편리하게 하기위한 dispatch_apply 함수를 제공한다.
이함수는 하나의 block을 병행적으로 여러번 호출하면서 완료되기를 기다린다.

1
2
3
4
5
6
7
8
9
dispatch_queue_t queue
  = dispatch_get_global_qeueue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_apply([array count], queue,
 ^(size_t index){ [self doSomethingIntensiveWith:
          [array objectAtIndex:index]];
});
//완료되었다.
[self doSomethingWith:array];
Pastie #2500421 linked directly from Pastie.

그렇다면 비동기적인 처리는 어떠한가?
dispatch_apply 의 비동기 버전은 존재하지 않는다. 하지만
dispatch_async 함수를 이용하여, 모든 작업을 백그라운드 작업(비동기)으로
만들 수 있다.

1
2
3
4
5
6
7
8
9
10
11
dispatch_queue_t queue
  = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue,
 ^{
        dispatch_apply([array count], queue,
        ^(size_t index){ [self doSomethingIntensiveWith:
             [array objectAtIndex:index]];
        });
        [self doSomethingWith:array];
    });
Pastie #2500424 linked directly from Pastie.

이러한 작업에서 중요한점은, 동시에 여러개의 데이터들에 대해서
동일한 작업을 하는 코드를 구분해내고, 또한 그작업이 thread safe하게
병행처리 될수 있는지 확인 하는 것이다.

이조건이 만족되면 GCD를 이용, 병행 처리가 가능하다. 성능 관련해서 GCD가
쓰레드보다 경량, 부하가 작다고는 하지만, block이 복사되어 큐에 들어가고,
적합한 작업 쓰레드가 처리하는 과정등.. 큐에 block을 제공 하는것은 여전히 비용이
드는 일이다.

예를 들어 어떤 이미지의 모든 픽셀처리를 위한 block을 큐에 제출 하는 것 은
별 도움이 되지 않을 것 이다. 하지만 여러 이미지들을 전환하기 위해 하나의
이미지 처리를 위한 block을 큐로 제출 하는 것 은 좋은 활용 방안이다.

GCD사용이 이득인지 아닌지 의심스러우면, 실험이 필요하다.
어플리케이션 병행처리란 즉 최적화란 의미이다.
실험 전후를 측정해서 병행 처리의 이득을 보고 있는지 꼭 확인해야 한다.

온전한 내용을 원하신다면 원문을 참조하기 바랍니다...

2011년 3월 24일 목요일

Intro to Grand Central Dispatch, Part I: Basics and Dispatch Queues 정리


참고 : www.mikeash.com

향후 아이패드2나 아이폰5 등 멀티코어 환경에서 개발을 하려는 사람들에게는
꼭 알아두어야 할 기술이 바로 이 GCD 가 아닐까 한다.

* 2011-10-20에 추가됨
이번에 IOS5 가 나오면서 GCD 에 변경된 부분이 있다.
간추려보면
1. 새로운 global queue 추가(DISPATCH_QUEUE_PRIORITY_BACKGROUND)
2. custom concurrent queue
3. Dispatch IO
자세한 사항은 여기를 참조

1. GCD (Grand Central Dispatch)

concurrent programming을 위한 저수준 C API.
작업을 분리하고 work queues에 저장후, 동시에 혹은 하나씩 작업을 처리.
(NSOperationQueue와 비슷하나 좀더 저수준이고 높은 성능을 보인다고 한다).

dispatch object =  GCD 는 객체지향 개념으로 만들어졌다. GCD 객체를 일컬음.

메모리 관리 필요 : dispatch_retain, dispatch_release

Dispatch Queues: 처리할 작업을 받아들이기 위한 객체. concurrent / serial 이 존재.
동시에 작업을 처리(시스템 부하를 고려하여)하거나 하나씩 작업을 처리한다.

3가지 큐 존재

Main queue: serial queue.
메인 쓰레드가 처리하게 된다.
dispatch_get_main_queue() 호출로 얻을수있다. 

Global queues: concurrent queues
전체 프로세스가 공유하며, 3종류 존재
(high,default,low priority queue).
동시에 작업을 수행한다.
dispatch_get_global_queue() 호출로 접근가능 (priority 지정필요).

Custom queues: serial queue.
한번에 하나씩 작업을 처리한다.
dispatch_queue_create()로 생성된다.

2. 사용법
  1) 큐를 생성 (위의 함수 호출 이용)
custom queue 경우 다음처럼 호출.

1
dispatch_queue_create("com.yourcompany.subsystem.task", NULL);
Pastie #2500485 linked directly from Pastie.
 
  2) 처리할 작업을 전달한다.
       
1
2
3
4
5
6
dispatch_async(dispatch_get_global_queue
  (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
 ^{
  [self goDoSomethingLongAndInvolved];
  NSLog(@"Done doing something long and involved");
 });
Pastie #2500430 linked directly from Pastie.
dispatch_async 함수는 즉시 리턴되며, 작업은 비동기적으로
백그라운드에서 수행된다.

* 중첩된 dispatche.
시간이 걸리는 작업 종료후 UI변경등이 필요한 경우
UI처리는 main thread 에서 수행되어야 하므로 중첩된 호출을 통해 가능함.
       
1
2
3
4
5
6
7
8
9
dispatch_async(dispatch_get_global_queue
  (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
 [self goDoSomethingLongAndInvolved];
     dispatch_async(dispatch_get_main_queue(),
     ^{
            [textField setStringValue:@"Done doing something long and involved"];
        });
});
Pastie #2500436 linked directly from Pastie.
처리할 작업(block)이 종료될때까지 기다리는 경우 dispatch_sync 사용.
__block 변수 : 살행중인 block으로부터 결과를 받을수 있음. 만약 백그라운드
작업중인 쓰레드에서 GUI 컴트롤 값을 필요로 하는 경우 다음처럼 사용가능.
       
1
2
3
4
5
6
7
8
9
10
11
12
13
//백그라운드 쓰레드내에서의 소스.
//현재 이쓰레드가 백그라운드로 작업중이라고 가정.
__block NSString *stringValue;
  
dispatch_sync(dispatch_get_main_queue(),
^{
        // __block 변수는 자동으로 retain 되지 않음.
        // reference 를 확실하게 만들어서 관리한다.
        stringValue = [[textField stringValue] copy];
});
  
[stringValue autorelease];  
// 그리고 stringValue를 백그라운드 쓰레드에서 사용한다.
Pastie #2500450 linked directly from Pastie.

쓰레드등을 이용한 백그라운드 프로세싱을 중첩된 block을 이용하여 아래처럼
교체가능하다.

1
2
3
4
5
6
7
8
9
dispatch_queue_t bgQueue = myQueue; //global 혹은 custom 큐.

dispatch_async(dispatch_get_main_queue(), ^{
    NSString *stringValue = [[[textField stringValue] copy] autorelease];
    dispatch_async(bgQueue, ^{
            // 이부분에 들어가는 소스는 백그라운드로 수행되며
            // stringValue 를 사용할수 있다.
    });
});
Pastie #2500459 linked directly from Pastie.
 3) Lock을 대체하기 위해서 이렇게도 사용 가능하다.
      일반적인 경우가 다음과 같다면,
   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
NSLock *lock;  
//getter
- (id)something
{
    id localSomething;
    [lock lock];
    localSomething = [[something retain] autorelease];
    [lock unlock];
    return localSomething;
}
//setter
- (void)setSomething:(id)newSomething
{
    [lock lock];
    if(newSomething != something)
    {
        [something release];
        something = [newSomething retain];
        [self updateSomethingCaches];
    }
    [lock unlock];
}
Pastie #2500472 linked directly from Pastie.
   
     다음처럼 바꿀수 있다.
     이때 큐는 custom (즉 serial)큐만 가능하다. 동기 /비동기 처리를
활용하는것임. 즉 , 비동기(dispatch_async)로 동기화가 필요한 함수를
호출하면  백그라운드에서는 dispatch_sync로 처리되어 serial하게
작업을 처리한다.
   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (id)something
{
    __block id localSomething;
    dispatch_sync(queue, ^{
         localSomething = [something retain];
    });
    return [localSomething autorelease];
}
    
- (void)setSomething:(id)newSomething
{
    dispatch_async(queue, ^{
        if(newSomething != something)
        {
            [something release];
            something = [newSomething retain];
            [self updateSomethingCaches];
        }
    });
}
Pastie #2500480 linked directly from Pastie.
        
dispatch queue 사용이 매우 가벼워서 lock을 거는 빈도가 많아도 무방함. 


더욱 온전한 내용을 원하신다면 원문을 참조하기 바랍니다...