objective c - Bloquear un objeto para que no sea accedido por mĂșltiples hilos-Objective-C
ios multithreading (3)
Al igual que en Swift 3 en la WWDC 2016 Session Session 720 Concurrent Programming With GCD en Swift 3 , debe usar la queue
class MyObject {
private let internalState: Int
private let internalQueue: DispatchQueue
var state: Int {
get {
return internalQueue.sync { internalState }
}
set (newValue) {
internalQueue.sync { internalState = newValue }
}
}
}
Tengo una pregunta sobre seguridad de hilos 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.
Mi pregunta es triple :
Supongamos que tengo una matriz, NSMutableArray *myAwesomeArray;
Fold 1:
Ahora @synchronized(myAwesomeArray){...}
si me equivoco, pero por lo que entiendo, el uso de @synchronized(myAwesomeArray){...}
evitará que dos subprocesos accedan al mismo bloque de código. Entonces, básicamente, si tengo algo como:
-(void)doSomething {
@synchronized(myAwesomeArray) {
//some read/write operation on myAwesomeArray
}
}
luego, si dos subprocesos acceden al mismo método al mismo tiempo, ese bloque de código estará protegido contra subprocesos. Supongo que he entendido esta parte correctamente.
Fold 2:
¿Qué hago si myAwesomeArray
está siendo accedido 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, ambos métodos son accedidos por dos hilos diferentes al mismo tiempo. ¿Cómo me myAwesomeArray
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 una databaseManagerInstance
cada vez que quiero realizar operaciones de base de datos, y la libero una vez que he terminado. 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, todos 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.
La subclase NSMutableArray proporciona bloqueo para 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, podría implementar addObject como:
- (void)addObject:(id)obj {
dispatch_sync (self.queue, ^{ [super addObject: obj] });
}
Nota: si usa bloqueos, seguramente necesitará NSRecursiveLock, no NSLock, porque no conoce las implementaciones de Objective-C de addObject, etc. son ellos mismos recursivos.
Fold 1 En general, tu 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. Verifica los documentos .
Fold 2 La mayoría de las personas probablemente te aconsejarían usar NSRecursiveLock. Si yo fuera tú, usaría GCD. Aquí hay un gran documento que muestra cómo migrar de la programación de subprocesos a la programación de GCD , 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 , solo 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. Si desea ver el enfoque basado en GCD en el contexto de programación de bases de datos, eche un vistazo a la implementación de fmdb . Hace exactamente lo que describí en Fold2 .
Como nota al margen de Fold 3 , no creo que la instancia de DatabaseManager cada vez que quiera usar la base de datos y luego liberarla sea 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 quieres usar GCD, entonces sí, necesitarás usar algún tipo de mecanismo de bloqueo, y sí, NSRecursiveLock
evitará interbloqueos si usas recurrencia en tus 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, entonces NSRecursiveLock
no es suficiente. Aún puede manejar esta situación con NSCondition
, pero NSCondition
, ahorrará mucho tiempo usando GCD en este caso. Si el orden de los hilos no es relevante, estás a salvo con bloqueos.