¿Cómo implementar un mecanismo de bloqueo reentrante en Objective-C a través de GCD?

Resuelto Daniel S. asked hace 11 años • 1 respuestas

Tengo una clase Objective-C con algunos métodos, que utilizan una cola GCD para garantizar que los accesos simultáneos a un recurso se realicen en serie (forma estándar de hacer esto).

Algunos de estos métodos necesitan llamar a otros métodos de la misma clase. Por lo tanto, el mecanismo de bloqueo debe volver a entrar. ¿Existe una forma estándar de hacer esto?

Al principio, utilicé cada uno de estos métodos.

dispatch_sync(my_queue, ^{

   // Critical section

});

para sincronizar accesos. Como sabe, cuando uno de estos métodos llama a otro método similar, se produce un punto muerto porque la llamada de despacho_sync detiene la ejecución actual hasta que se ejecuta ese otro bloque, que tampoco se puede ejecutar porque se detiene la ejecución en la cola. Para resolver esto, utilicé, por ejemplo, este método:

- (void) executeOnQueueSync:(dispatch_queue_t)queue : (void (^)(void))theBlock {
    if (dispatch_get_current_queue() == queue) {
        theBlock();
    } else {
        dispatch_sync(queue, theBlock);
    }
}

Y en cada uno de mis métodos, uso

[self executeOnQueueSync:my_queue : ^{

   // Critical section

}];

No me gusta esta solución porque para cada bloque con un tipo de retorno diferente, necesito escribir otro método. Además, este problema me parece muy común y creo que debería existir una solución estándar mejor para ello.

Daniel S. avatar Oct 21 '13 19:10 Daniel S.
Aceptado

Lo primero es lo primero: dispatch_get_current_queue()está en desuso. El enfoque canónico ahora sería utilizar dispatch_queue_set_specific. Un ejemplo de ello podría verse así:

typedef dispatch_queue_t dispatch_recursive_queue_t;
static const void * const RecursiveKey = (const void*)&RecursiveKey;

dispatch_recursive_queue_t dispatch_queue_create_recursive_serial(const char * name)
{
    dispatch_queue_t queue = dispatch_queue_create(name, DISPATCH_QUEUE_SERIAL);
    dispatch_queue_set_specific(queue, RecursiveKey, (__bridge void *)(queue), NULL);
    return queue;
}

void dispatch_sync_recursive(dispatch_recursive_queue_t queue, dispatch_block_t block)
{
    if (dispatch_get_specific(RecursiveKey) == (__bridge void *)(queue))
        block();
    else
        dispatch_sync(queue, block);
}

Este patrón es bastante utilizable, pero podría decirse que no es a prueba de balas, porque podrías crear colas recursivas anidadas con dispatch_set_target_queue, y tratar de poner en cola el trabajo en la cola externa desde dentro de la interna se bloquearía, aunque ya estés "dentro de la cerradura" (en comillas de burla porque solo parece un candado, en realidad es algo diferente: una cola (de ahí la pregunta, ¿no?) para el exterior. (Puede solucionar este problema ajustando las llamadas dispatch_set_target_queuey manteniendo su propio gráfico de orientación fuera de banda, etc., pero eso se deja como ejercicio para el lector).

Continúas diciendo:

No me gusta esta solución porque para cada bloque con diferentes tipos de retorno, necesito escribir otro método.

La idea general de este patrón de "cola en serie que protege el estado" es que estás protegiendo el estado privado; ¿Por qué "traerías tu propia cola" a esto? Si se trata de múltiples objetos que comparten la protección estatal, entonces proporcióneles una forma inherente de encontrar la cola (es decir, introdúzcala en el momento inicial o colóquela en algún lugar que sea mutuamente accesible para todas las partes interesadas). No está claro cómo sería útil aquí "traer su propia cola".

ipmcc avatar Oct 21 '2013 13:10 ipmcc