본문 바로가기
프로그래밍 놀이터/iOS

[iOS Study] 병렬 프로그래밍 가이드 ( dispatch source )

by 돼지왕 왕돼지 2017. 7. 3.
반응형

 [iOS Study] 병렬 프로그래밍 가이드 ( dispatch source )


https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/GCDWorkQueues/GCDWorkQueues.html#//apple_ref/doc/uid/TP40008091-CH103-SW1


Absolute, animation, App, Async, Backlogged, best condition, Block, block object, calcellation handler, cancel, cancellation handler, config, context pointer, copy retain, core wake up, custom dispatch source, customsource, Data Structure, Data Type, DeadLock, deallocated, define, Delete, descriptor, descriptor source, Descriptor 에 data 쓰기, Descriptor 에서 data 읽기, dispatch queue, dispatch source, dispatch source object, Dispatch source 만들기, Dispatch source 에 custom data 묶기, dispatch_after, dispatch_after_f, DISPATCH_PROC_EXIT, DISPATCH_QUEUE_PRIORITY_DEFAULT, dispatch_release, dispatch_resume, dispatch_retain, dispatch_set_context, dispatch_set_target_queue, dispatch_source_cancel, dispatch_source_cnode_flags_t, dispatch_source_create, dispatch_source_get_data, dispatch_source_get_handle, dispatch_source_get_mask, dispatch_source_machport_flags_t, dispatch_source_mach_send_flags_t, dispatch_source_merge_data, dispatch_source_proc_flags_t, dispatch_source_set_cancel_handler, dispatch_source_set_cancl_handler_f, dispatch_source_set_event_handler, dispatch_source_set_event_handler_f, dispatch_source_Set_timer, dispatch_source_t, DISPATCH_SOURCE_TYPE_PROC, DISPATCH_SOURCE_TYPE_READ, DISPATCH_SOURCE_TYPE_SIGNAL, DISPATCH_SOURCE_TYPE_TIMER, DISPATCH_SOURCE_TYPE_VNODE, DISPATCH_SOURCE_TYPE_WRITE, dispatch_time, DISPATCH_TIME_NOW, DISPATCH_VNODE_RENAME, dispatch_walltime, enumerated type, event, Event Handler, Event Handler 만들고 설정하기, exec, fcntl, file, File-system object 감시하기, flag, fork, Free, function, function event handler, GCD, grande central dispatch, handler, iNT, Latency, leeway, long-lived, low-level system event, Mach, mach port, mach port dispatch source, malloc, meta info, moved, network error, non-blocking operation, noti, Notify, old event, option, optional, O_EVTONLY, parameter, pending data, pid_t, pointer, power manage, process, process dispatch source, process exit, Process 모니터링 하기, Queue, Read, reference count, relative, release, Rename, return value, Second, serial queue, server upodate, sigaction, SIGBUS, SIGHUP, SIGKILL, Signal, signal dispatch source, signal handler, Signal 감시하기, signalid, SIGSEGV, SIG_IGN, sleep, socket, source, suspended, sync action, system clock, system data type, system event, Target Queue 변경하기, task, timer, timer dispatch source, total signal count, trigger, unix signal, Wake up, write, [iOS Study] 병렬 프로그래밍 가이드 ( dispatch source ), 감시, 감지불가능, 게임, 단말 sleep 모드, 병렬 프로그래밍, 삭제, 새로운 event, 성능 장애, 수행중, 에러 시그널, 정보 갱신, 제거, 주기적인 noti, 통합


About dispatch source


-

dispatch source 는 low-level system event 를 처리하기 위한 data type 이다.



-

Timer dispatch source 는 주기적인 noti 를 만든다.



-

Signal dispatch source 는 UNIX signal 이 도착하면 noti 를 한다.



-

Descriptor source 는 file, socket base operation 에 따라 notify 를 한다.

(  read, write 가능할 때, delete, moved, rename 되었을 때, meta info 가 변경되었을 떄 )



-

Process dispatch source 는 process 관련된 event 에 notify 한다.

( process 가 있을 때, fork 나 exec 가 수행되었을 때, process 에 signal 이 도착했을 때 )



-

Mach port dispatch source 는 Mach 관련된 event 에 noti 를 한다.



-

Custom dispatch source 는 당신이 define 하고 trigger 하는 것에 noti 한다.



-

특별 system event 에 대해 dispatch queue 를 등록해 놓고 감시를 수행할 수 있다.

해당 event 에 대해 block object 를 등록해놓으면, 해당 event 가 도착해면 해당 dispatch queue 에 등록한 block 을 전달한다.



-

dispatch source 는 cancel 할 때까지 계속 붙어있으면서 해당 task 가 계속 불린다.



-

만약 새로운 event 가 도착했는데, 이전에 도착한 event 에 대한 block 이 수행되지 않았다면 (backlogged)

dispatch source 는 새로운 event 와 이전 event 를 합쳐버린다.

event 종류에 따라 old event 를 제거하고 새로운 event 로 정보를 갱신할 수 있다.

물론 그냥 없애지는 않고, 기존에 왔던 signal 에 대한 것은 total signal count 등으로 알려주기는 한다.




Dispatch source 만들기


-

dispatch_source_create 함수를 통해 dispatch source 를 만든다.

event handler 를 dispatch source 에 등록한다.

( timer 의 경우 dispatch_source_set_timer 를 통해 등록한다. )

option 으로 cancellation handler 를 dispatch source 에 등록할 수 있다.

dispatch_resume 함수를 통해 event 에 대한 처리를 개시시킬 수 있다.



-

dispatch source 는 추가적인 config 가 필요하기 때문에

dispatch_source_create 는 dispatch source 를 suspended state 상태로 전달한다.

suspended 상태일 때는 dispatch source 가 event 를 받아도 이를 처리하지 않는다.

suspended 상태인 이유는 event handler 설정을 실제 event 가 도착하기 전에 하기 위함이다.




Event Handler 만들고 설정하기


-

dispatch source 를 만들 때는 event 를 처리한 event handler 를 만들어서 전달해야 한다.

event handler 는 function 이나 block object 이다.

dispatch_source_set_event_handler 나 dispatch_source_set_event_handler_f 를 통해 설정 가능하다.



-

만약 어떤 event processing 을 위해서 event handler 가 이미 queue 에 들어있는데 process 되지 않은 상태라면,

그 다음에 들어온 같은 종류의 event 는 통합이 된다.

event 의 종류에 따라 최근것만 바라볼 수도 있고, 통합된 다른 event 정보들을 볼 수도 있다.


만약 특정 event 에 대한 event handler 가 이미 수행중이라면,

queue 에 넣지 않고 해당 handler 의 기능이 끝나기를 기다렸다가 끝나면 queue 에 다시 넣는다.



-

function event handler 는 context pointer 만을 전달받는다.

이 context pointer 에는 dispatch source object 가 들어있다.

그리고 이 function 은 return value 가 없다.



-

block event handler 는 parameter 도 없고, return value 도 없다.



-

function 은 context pointer 를 전달받지만, block 은 그렇지 않아서 스스로 구해야 한다.


dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, myDescriptor, 0, myQueue);

dispatch_source_set_event_handler(source, ^{

     size_t estimated = dispatch_source_get_data(source);


     // do something..

});

dispatch_resume(source);



-

block 이나 function 안에서 해당 source 에 대한 정보를 modify 할 수 있지만, 그렇게 하는 것은 권장되지 않는다.

event handler 실행이 async 로 발생하기 떄문에 위험하기 때문이다.



-

아래는 event handler 안에서 event 에 대한 정보를 얻기 위한 함수들이다.


dispatch_source_get_handle

     dispatch source 가 관리하는 system data type 를 return 한다.

     descriptor 는 descriptor 를 표시하는 int return

     signal 은 가장 최근의 signalId의 number 를 표시하는 int return

     process 는 감시되고 있는 process 의 pid_t 를 return.

     mach port 는 mach_port_t 라는 data structure 를 return.


dispatch_source_get_data

     해당 event 에 대해 pending data 를 return 한다.

     descriptor 는 read 의 경우 read 가능한 byte 수, write 의 경우 write 가능한 공간 int.

          그 외의 경우는 descriptor 에서 발생하는 event 의 dispatch_source_cnode_flags_t 형태의 enumerated type 이 return 된다.

     process 의 경우 process 에 발생한 dispatch_source_proc_flags_t 형태의 enumerated type 이 return.

     Mach port 의 경우 dispatch_source_machport_flags_t 형태의 enumerated type 이 return.

     Custom source 의 경우에는 dispatch_source_merge_data 함수를 통해 전달된 data 가 전달된다.


dispatch_source_get_mask

     dispatch source 를 생성하는데 사용된 event 의 flag 가 전달된다.

     process 의 경우 dispatch_source_proc_flags_t 형태의 enumerated type 이 return.

     mach port 의 경우 dispatch_source_mach_send_flags_t 형태의 enumerated type 이 return.

     custom source 의 경우 data value 를 merge 하는데 사용된 mask 가 return.




Cancellation Handler 설치하기


-

cancellation handler 는 대부분 optional 이다.

하지만 descriptor 나 Mach port 의 경우는 descriptor 나 Mach port 의 release 를 위해서 반드시 제공해야 한다.



-

cancellation handler 는 아무때나 설치 가능하지만, 보통 dispatch source 를 만들 때 설치한다.

dispatch_source_set_cancel_handler 나 dispatch_source_set_cancel_handler_f 함수를 통해 수행한다.


dispatch_source_set_cancel_handler(mySource, ^{

     close(fd);

});






Target Queue 변경하기


-

dispatch_set_target_queue 를 통해서 언제든 target queue 를 바꿀 수도 있다.

이 queue 를 바꾸는 것은 asnyc 로 작동한다.

dispatch source 는 가능한한 빨리 이 queue 를 바꾸려 하지만 호출시점에 바로 적용되지는 않는다.


만약 dispath source 가 이미 queue 가 되어 있다면 그 녀석은 기존에 설정된 queue 에서 수행되고,

새로 적용한 queue 는 다음부터 적용된다.




Dispatch source 에 custom data 묶기


-

다른 GCD(Grand Central Dispatch)와 같이 dispatch_set_context 함수를 통해 custom data 를 dispatch source 와 결부시킬 수 있다.

context pointer 에 어떤 정보든 저장시킬 수 있다.



-

context pointer 에 어떤 정보를 저장시키려면 항상 cancellation handler 를 동반하여,

cancellation handler 에서 적절히 data 를 release 시켜주어야 한다.



-

block 을 사용하는 경우에는 event handler 에서 local 정보를 참조할 수 있다.

이것이 context pointer 에 정보 저장하는 것의 사용성을 낮추지만, 잘 분별해서 사용해야 한다.

dispatch source 는 app 에서 long-lived 될 수 있기 때문에 pointer 를 참조하는 녀석들은 조심해서 써야 한다.

이 pointer 들은 언제 deallocated 될지 모르기 때문이다.

그래서 보통 copy & retain 하여 이런 변수를 차단하는 것이 좋다.



-

다른 dispatch queue 처럼 dispatch source 도 dispatch_retain 과 dispatch_release 를 통해 reference count 가 조정될 수 있다.

dispatch source 는 최초에 reference count 가 자동 1 이 된다.

release 가 제대로 되면 system 이 reference count 가 0 이 되는 순간 dispatch source 를 deallocate 한다.




Timer 만들기


-

timer dispatch source 는 시간 기반의 주기적 event 를 생성한다.

규칙적으로 무언가를 실행시키기 위해서 이 timer 를 사용할 수 있다.

보통 game 같은 곳에서 화면 갱신하거나 animation 등을 하기 위해 잘 사용한다.

주기적인 server update 등에도 사용한다.



-

timer 를 설정할 때 leeway 값을 잘 설정하면,

얼마나 정확하게 timer 를 수행할지, core wake up 을 어떻게 할지, power manage 를 어떻게 할지 등을 조정할 수 있다.

leeway 값( second )이 0이라고 해도 완전 정확한 시간에 timer 가 불리는 것은 아니다. best condition 으로 fire 한다.



-

단말이 sleep 모드에 들어가면 모든 timer dispatch source 는 suspend 된다.

단말이 wake up 하면 해당 timer dispatch source 는 자동으로 활성화된다.



-

dispatch_time 함수에 DISPATCH_TIME_NOW 상수를 전달하면,

dispatch source 는 기본 system clock 을 사용하여 fire time 을 조정한다.

그러나 system clock 은 단말이 sleep 상태일 때 정확하지 않을 수 있다.


반대로 dispatch_walltime 함수를 호출한다면,

timer dispatch source 는 wall clock time 을 기반으로 한다.

wall time 은 interval 이 상대적으로 큰 경우에 적합하다.



-

uint64_t interval = xx;

uint64_t leeway = xx;

dispatch_block_t block = ^{

     // do something..

};


dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue() );

if ( timer ){

     dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);

     dispatch_source_set_event_handler(timer, block);

     dispatch_resume(timer);

}



-

주기적인 time event 를 받는 것이 아닌, 특정 시간 이후 한번이라는 조건으로 어떤 event 를 수행시키고 싶다면,

dispatch_after 나 dispatch_after_f 함수를 통해서 block 을 전달하면 된다.

이 때 time value 는 absolute 나 relative 어떤 것이든 될 수 있다.




Descriptor 에서 data 읽기


-

file 이나 socket 으로부터 data 를 읽기 위해서 dispatch source 를 만들 때 type 을 DISPATCH_SOURCE_TYPE_READ 로 설정해야 한다.



-

data 를 읽을 때 항상 descriptor 가 non-blocking operation 을 하도록 설정해야 한다.

dispatch_source_get_data 함수를 통해 얼마나 읽을 수 있는지 알 수 있지만, 이 값은 이 함수를 부르는 시점과 실제 값을 읽는 시점의 값이 다를 수 있다.

그래서 network error 가 난다거나 하는 예외상황에서 실행이 멈출 수 있다.

이는 성능장애로 이어지고, serial queue 에서는 deadlock 이 될 수 있다.



-

int fd = open(filename, O_RDONLY);

if ( fd != -1 ){

     fcntl(fd, F_SETFL, O_NONBLOCK); // avoid blocking the read operation

}


dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_source_t readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, queue);

if ( readSource ){

     dispatch_source_set_event_handler(readSource, ^{

          size_t estimated = dispatch_source_get_data(readSource) + 1;

          char* buffer = (char*)malloc(estimated);

          if ( buffer ){

               ssize_t actual = read(fd, buffer, estimated);

               Boolean isProcessSuccess = doSomething(buffer, actual);

               free(buffer);

               if (isProcessSuccess){

                    dispatch_source_cancel(readSource);

               }

          }

     });


     dispatch_source_set_cancel_handler(readSource, ^{

          close(fd);

     });


     dispatch_resume(readSource);

}




Descriptor 에  data 쓰기


-

descriptor 에서 read 하기와 비슷하다.

source type 은 DISPATCH_SOURCE_TYPE_WRITE 를 사용한다.



-

int fd = open( filaname, O_WRONLY | O_CREATE | O_TRUNC, (S_IRUSR | S_IWUSR | S_ISUID | S_ISGID ) );

if ( fd != -1 ){

     fcntl(fd, F_SETFL, O_NONBLOCK); // non block during write


     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

     dispatch_source_t writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, fd, 0, queue);

     

     if ( writeSource ){

          dispatch_source_set_event_handler(writeSource, ^{

               size_t bufferSize = xx;

               void* buffer = malloc(bufferSize);


               size_t actual = xx;

               write(fd, buffer, actual);

          

               free(buffer);


               dispatch_source_cancel(writeSource);

          });


          dispatch_source_set_cancel_handler(writeSource, ^{

               close( fd );

          });

          dispatch_resume(writeSource);

     }

}






File-system object 감시하기


-

type 은 DISPATCH_SOURCE_TYPE_VNODE 를 사용한다.

삭제되거나, 쓰여지거나, rename 되는 등의 파일에 변화가 있을 때 불린다.

meta info 가 바뀌는 경우에도 불린다.



-

int fd = open(filename, O_EVTONLY);

if ( fd != -1 ){

     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

     dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, DISPATCH_VNODE_RENAME, queue);

     

     if ( source ){

          int length = strlen(filename);

          char* newString = (char*)malloc(length + 1);

          newString = strcpy(newString, filename);

          dispatch_set_contet(source, newString);


          dispatch_source_set_event_handler(source, ^{

               const char* oldFilename = (char*)dispatch_get_context(source);

               // do something;

          });


          dispatch_source_set_cancel_handler(source, ^{

               char* fileStr = (char*)dispatch_get_context(source);

               free(fileStr);

               close(fd);

          });


          dispatch_resume(source);

     }

}




Signal 감시하기


-

UNIX signal 은 복구할 수 없는 에러 시그널부터, 중요한 정보( 예를 들면 process exit ) 전달까지 여러가지 signal 을 보낸다.



-

전통적으로 app 은 sigaction 함수를 통해 signal handler 를 설정해왔다.

sigaction 은 그러나 sync action 이다.



-

signal dispatch source 는 SIGKILL, SIGBUS, SIGSEGV signal 은 감지 불가능하다.



-

sigaction 과 비교하여 signal dispatch source 는 자유도는 올라가지만 그만큼 latency 는 발생할 수 있다.

덧붙여 위에 언급한 signal 들은 전달받지 못한다.


sigaction 은 signal 들을 받아들여 app 이 종료되지 않도록 처리할 수 있지만,

signal dispatch source 는 그런 것은 할 수 없고, 감지만 할 수 있다.



-

signal(SIGHUP, SIG_IGN);


dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGHUP, 0, queue);


if ( source ){

     dispatch_source_set_event_handler(source, ^{

          // do something;

     });


     dispatch_resume(source);

}




Process 모니터링 하기


-

pid_t parentPID = xx;


dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, parentPID, DISPATCH_PROC_EXIT, queue);


if ( source ){

     dispatch_source_set_event_handler(source, ^{

          // do something..

          dispatch_source_cancel(source);

          dispatch_release(source);

     });

     dispatch_resume(source);

}






반응형

댓글