Grand Central Dispatch ( GCD ) es la tecnología de Apple para crear aplicaciones que aprovechan los procesadores multinúcleo y otros sistemas SMP [1] . Esta tecnología es una implementación del paralelismo de tareas y se basa en el patrón de diseño Thread Pool . GCD se introdujo por primera vez con Mac OS X 10.6 . El código fuente de la biblioteca libdispatch que implementa los servicios GCD se publicó bajo la licencia de Apache el 10 de septiembre de 2009. [ 1] Archivado el 2 de noviembre de 2009 en Wayback Machine .. Posteriormente, la biblioteca fue portada [2] a otro sistema operativo FreeBSD [3] .
GCD le permite definir tareas en una aplicación que se pueden ejecutar en paralelo y las ejecuta cuando hay recursos informáticos libres ( núcleos de procesador ) [4] .
Una tarea se puede definir como una función o como un " bloque ". [5] Un bloque es una extensión de sintaxis no estándar de los lenguajes de programación C / C ++ / Objective-C que encapsula código y datos en un solo objeto, de forma análoga a un cierre . [cuatro]
Grand Central Dispatch usa subprocesos a un nivel bajo, pero oculta los detalles de implementación al programador. Las tareas de GCD son livianas, económicas de crear y cambiar; Apple afirma que agregar una tarea a la cola requiere solo 15 instrucciones del procesador , mientras que crear un hilo tradicional cuesta varios cientos de instrucciones. [cuatro]
Una tarea GCD se puede usar para crear un elemento de trabajo que se coloca en una cola de tareas o se puede vincular a un origen de eventos. En el segundo caso, cuando se activa el evento, la tarea se agrega a la cola adecuada. Apple afirma que esta opción es más eficiente que crear un hilo separado esperando que se active el evento.
El marco GCD declara varios tipos de datos y funciones para crearlos y manipularlos.
Se pueden encontrar dos ejemplos que demuestran la facilidad de uso de Grand Central Dispatch en la revisión de Snow Leopard de John Syracuse en Ars Technica . [6] .
Inicialmente, tenemos una aplicación con un método de análisis de documentos que cuenta palabras y párrafos en un documento. Por lo general, el proceso de contar palabras y párrafos es lo suficientemente rápido y se puede realizar en el hilo principal sin temor a que el usuario note una demora entre presionar el botón y obtener el resultado:
- ( IBAction ) analizarDocumento: ( NSButton * ) remitente { NSDictionary * stats = [ analizar myDoc ]; [ myModel setDict : estadísticas ]; [ myStatsView setNeedsDisplay : SÍ ]; }Si el documento es muy grande, entonces el análisis puede tomar bastante tiempo para que el usuario note el "bloqueo" de la aplicación. El siguiente ejemplo facilita la solución de este problema:
- ( AcciónIBA ) analizarDocumento :( BotónNS * ) remitente { dispatch_async ( dispatch_get_global_queue ( 0 , 0 ), ^ { NSDictionary * stats = [ analizar myDoc ]; dispatch_async ( dispatch_get_main_queue (), ^ { [ myModel setDict : estadísticas ]; [ myStatsView setNeedsDisplay : SÍ ]; }); }); }Aquí, la llamada [analizar myDoc] se coloca en un bloque, que luego se coloca en una de las colas globales. Una vez que se completa [myDoc analysis], se coloca un nuevo bloque en la cola principal, que actualiza la interfaz de usuario . Al realizar estos cambios simples, el programador evitó el bloqueo potencial de la aplicación al analizar documentos grandes.
El segundo ejemplo demuestra la paralelización del ciclo:
for ( i = 0 ; i < cuenta ; i ++ ) { resultados [ i ] = hacer_trabajo ( datos , i ); } total = resumir ( resultados , contar );Aquí, la función do_work se llama count times, el resultado de su ejecución i-th se asigna al elemento i-th de la matriz de resultados, luego se resumen los resultados. No hay motivo para creer que do_works se basa en los resultados de llamadas anteriores, por lo que no hay nada que impida hacer varias llamadas a do_works en paralelo. La siguiente lista demuestra la implementación de esta idea usando GCD:
dispatch_apply ( cuenta , dispatch_get_global_queue ( 0 , 0 ), ^ ( size_t i ){ resultados [ i ] = hacer_trabajo ( datos , i ); }); total = resumir ( resultados , contar );En este ejemplo, dispatch_apply ejecuta el conteo de veces que el bloque le pasó, colocando cada llamada en la cola global y pasando los números de bloque de 0 a conteo-1. Esto permite que el sistema operativo seleccione la cantidad óptima de subprocesos para aprovechar al máximo los recursos de hardware disponibles. dispatch_apply no regresa hasta que se hayan completado todos sus bloques, para garantizar que todo el trabajo del bucle original se haya completado antes de llamar a summary.
El desarrollador puede crear una cola en serie separada para tareas que deben ejecutarse secuencialmente pero que pueden ejecutarse en un subproceso separado. Se puede crear una nueva cola así:
dispatch_queue_t ejemploCola ; exampleQueue = dispatch_queue_create ( "com.example.unique.identifier" , NULL ); // exampleQueue se puede usar aquí. dispatch_release ( ejemploCola );Evite colocar una tarea de este tipo en una cola secuencial que coloque otra tarea en la misma cola. Se garantiza que esto resultará en un interbloqueo . La siguiente lista muestra un caso de tal interbloqueo:
dispatch_queue_t exampleQueue = dispatch_queue_create ( "com.example.unique.identifier" , NULL ); dispatch_sync ( cola de ejemplo , ^ { dispatch_sync ( cola de ejemplo , ^ { printf ( "Ahora estoy estancado... \n " ); }); }); dispatch_release ( ejemploCola );