2012-06-01 13 views
35

Tengo una pregunta con respecto a la seguridad de subprocesos en Objective-C. He leído un par de otras respuestas, alguna de la documentación de Apple, y todavía tengo algunas dudas con respecto a esto, así que pensé en hacer mi propia pregunta.Bloquear un objeto para que no sea accedido por varios subprocesos - Objective-C

Mi pregunta es triple:

Supongamos que tengo una matriz, NSMutableArray *myAwesomeArray;

Fold 1:

Ahora me corrija si estoy equivocado, pero por lo que entiendo , utilizando @synchronized(myAwesomeArray){...} evitará que dos subprocesos accedan al mismo bloque de código. Así que, básicamente, si tengo algo como:

-(void)doSomething { 
    @synchronized(myAwesomeArray) { 
     //some read/write operation on myAwesomeArray 
    } 
} 

entonces, si dos subprocesos tienen acceso a la mismo método en el momento misma, ese bloque de código se hilo de seguridad. Supongo que he entendido esta parte correctamente.

Fold 2:

¿Qué hago si se está accediendo myAwesomeArray por múltiples hilos de diferentes métodos? Si tengo algo como:

- (void)readFromArrayAccessedByThreadOne { 
    //thread 1 reads from myAwesomeArray 
} 

- (void)writeToArrayAccessedByThreadTwo { 
    //thread 2 writes to myAwesomeArray 
} 

Ahora, tanto los métodos se accede por dos hilos diferentes al mismo tiempo. ¿Cómo me aseguro de que myAwesomeArray no tenga problemas? ¿Utilizo algo como NSLock o NSRecursiveLock?

Fold 3:

Ahora, en los dos casos anteriores, myAwesomeArray era un Ivar en la memoria. ¿Qué ocurre si tengo un archivo de base de datos que no siempre guardo en la memoria? Creo un databaseManagerInstance cada vez que quiero realizar operaciones de bases de datos, y lo libero una vez que termino. Por lo tanto, básicamente, diferentes clases pueden acceder a la base de datos. Cada clase crea su propia instancia de DatabaseManger, pero básicamente, todas usan el mismo archivo de base de datos. ¿Cómo me aseguro de que los datos no estén dañados debido a las condiciones de carrera en una situación así?

Esto me ayudará a aclarar algunos de mis fundamentos.

+1

'@ synchronize' impide que otros hilos accedan a la misma variable que se ha bloqueado, no bloque que de código específica. –

+0

Ah. Ya veo. Bueno, supongo que había algo más de lo que había entendido sobre la directiva @synchronize. ¡Gracias! : D – codeBearer

+0

@codeBearer He respondido su pregunta en http://stackoverflow.com/a/15393623/412916 Un poco. – Jano

Respuesta

41

Fold 1 Por lo general, su comprensión de lo que @synchronized hace es correcta. Sin embargo, técnicamente, no hace ningún código "thread-safe". Impide que diferentes hilos soliciten el mismo bloqueo al mismo tiempo, sin embargo, debe asegurarse de usar siempre el mismo token de sincronización al realizar secciones críticas. Si no lo haces, aún puedes encontrarte en la situación en la que dos hilos realizan secciones críticas al mismo tiempo. Check the docs.

Fold 2 La mayoría de las personas probablemente te aconsejarían usar NSRecursiveLock. Si yo fuera tú, usaría GCD. Here is a great document showing how to migrate from thread programming to GCD programming, creo que este enfoque al problema es mucho mejor que el basado en NSLock. En pocas palabras, crea una cola en serie y envía sus tareas a esa cola. De esta forma, se asegura de que sus secciones críticas se manejen en serie, por lo que solo se realiza una sección crítica en un momento determinado.

Fold 3 Esto es lo mismo que Fold 2, sólo que más específico. La base de datos es un recurso, de muchas maneras es lo mismo que la matriz o cualquier otra cosa. If you want to see the GCD based approach in database programming context, take a look at fmdb implementation. Hace exactamente lo que describí en Fold2.

Como nota al margen de Fold 3, no creo que una instancia de DatabaseManager cada vez que desee utilizar la base de datos y luego soltarlo es el enfoque correcto. Creo que deberías crear una única conexión de base de datos y conservarla a través de tu sesión de aplicación. De esta forma, es más fácil de administrar. Nuevamente, fmdb es un gran ejemplo de cómo se puede lograr esto.

Editar Si no desea usar GCD entonces sí, tendrá que utilizar algún tipo de mecanismo de bloqueo, y sí, NSRecursiveLock evitará estancamientos si utiliza la recursividad en sus métodos, por lo que es una buena opción (es usado por @synchronized). Sin embargo, puede haber una captura. Si es posible que muchos subprocesos esperen el mismo recurso y el orden en el que obtienen acceso sea relevante, NSRecursiveLock no es suficiente. Todavía puede manejar esta situación con NSCondition, pero créame, ahorrará mucho tiempo usando GCD en este caso. Si el orden de los hilos no es relevante, estás a salvo con bloqueos.

+0

¡Gracias por tu respuesta! Supongo que esto soluciona la mayoría de mis dudas. Solo como confirmación: supongo que usaría 'NSRecursiveLock' para Fold 3 también, si no fuera a usar GCD, ¿no? Esto es más para mi base de conocimiento, por lo que tengo mis conceptos claros. Además, gracias por su nota lateral en _Fold 3_. Estoy de acuerdo con usted en el hecho de que es mejor tener una clase retenida, y abrir y cerrar la conexión cuando sea necesario. Reescribí un código que creaba varias instancias, qué pesadilla era eso. > _ codeBearer

+0

Feliz de ayudar, eche un vistazo a mi respuesta editada. – lawicko

+0

¡Perfecto! ¡Muchísimas gracias de nuevo! : D myKnowledge ++ He estado planeando comenzar a usar GCD en todos nuestros proyectos nuevos, ¡y los enlaces que proporcionó son un excelente punto de partida! :) – codeBearer

1

Subclase NSMutable Arregle para proporcionar el bloqueo de los métodos de acceso (lectura y escritura). Algo como:

@interface MySafeMutableArray : NSMutableArray { NSRecursiveLock *lock; } @end 

@implementation MySafeMutableArray 

- (void)addObject:(id)obj { 
    [self.lock lock]; 
    [super addObject: obj]; 
    [self.lock unlock]; 
} 

// ... 
@end 

Este enfoque encapsula el bloqueo como parte de la matriz. Los usuarios no necesitan cambiar sus llamadas (pero deben tener en cuenta que podrían bloquear/esperar el acceso si el acceso es de tiempo crítico). Una ventaja importante de este enfoque es que si decide que prefiere no utilizar bloqueos, puede volver a implementar MySafeMutableArray para usar las colas de envío, o lo que sea mejor para su problema específico. Por ejemplo, se podría implementar addObject como:

- (void)addObject:(id)obj { 
    dispatch_sync (self.queue, ^{ [super addObject: obj] }); 
} 

Nota: si el uso de bloqueos, seguramente necesitará NSRecursiveLock, no NSLock, porque no se sabe de las implementaciones de Objective-C de addObject, etc, son ellos mismos recursivo

+0

Gracias por su respuesta. Esta es una buena idea si tuviera que crear una subclase segura para subprocesos de un objeto. :) – codeBearer

+1

Si deja la seguridad del hilo a las personas que llaman (es decir, no está haciendo una subclase segura para subprocesos, como sea que se haya implementado), entonces, se repetirá, se estará preparando para problemas. Te servirías a ti mismo para aprender un poco sobre la tecnología Spin. Buena suerte. – GoZoner

+0

Impresionante. Gracias por ese consejo. Lo investigaré. : D – codeBearer

Cuestiones relacionadas