Vedo un deadlock intermittente nella mia app quando si utilizza dispatch_sync su un dispatch_queue personalizzato. Sto usando qualcosa di simile al metodo descritto in Mike Ash's blog per supportare l'accesso in lettura simultaneo ma le mutazioni thread-safe su un NSMutableDictionary che funge da cache delle richieste RPC di rete attualmente attive. Il mio progetto utilizza ARC.Perché è dispatch_sync su deadlocking coda coda concorrente personalizzato
ho creare la coda con:
dispatch_queue_t activeRequestsQueue = dispatch_queue_create("my.queue.name",
DISPATCH_QUEUE_CONCURRENT);
e il dizionario mutabile con
NSMutableDictionary *activeRequests = [[NSMutable dictionary alloc] init];
ho letto gli elementi dalla coda in questo modo:
- (id)activeRequestForRpc: (RpcRequest *)rpc
{
assert(![NSThread isMainThread]);
NSString * key = [rpc getKey];
__block id obj = nil;
dispatch_sync(activeRequestsQueue, ^{
obj = [activeRequests objectForKey: key];
});
return obj;
}
ho aggiungere e rimuovere RPC dalla cache
- (void)addActiveRequest: (RpcRequest *)rpc
{
NSString * key = [rpc getKey];
dispatch_barrier_async(activeRequestsQueue, ^{
[activeRequests setObject: rpc forKey: key];
});
}
- (void)removeActiveRequest: (RpcRequest *)rpc
{
NSString * key = [rpc getKey];
dispatch_barrier_async(activeRequestsQueue, ^{
[activeRequests removeObjectForKey:key];
});
}
Sto vedendo il deadlock nella chiamata a activeRequestForRpc quando faccio molte richieste di rete in una volta che mi porta a credere che uno dei blocchi di barriera (aggiungere o rimuovere) non sta completando l'esecuzione. Io chiamo sempre activeRequestForRpc da un thread in background e l'interfaccia utente dell'app non si blocca quindi non penso che debba bloccare il thread principale, ma ho aggiunto l'istruzione assert per ogni evenienza. Qualche idea su come questo stallo potrebbe accadere?
UPDATE: l'aggiunta di codice che sta chiamando questi metodi
sto usando AFNetworking per fare le richieste di rete e ho un NSOperationQueue che sto programmando il 'cache di controllo e magari recuperare risorse dalla rete' logica. Chiamerò questo su CheckCacheAndFetchFromNetworkOp. All'interno di questo op faccio una chiamata alla mia sottoclasse personalizzata di AFHTTPClient per fare una richiesta RPC.
// this is called from inside an NSOperation executing on an NSOperationQueue.
- (void) enqueueOperation: (MY_AFHTTPRequestOperation *) op {
NSError *error = nil;
if ([self activeRequestForRpc:op.netRequest.rpcRequest]) {
error = [NSError errorWithDomain:kHttpRpcErrorDomain code:HttpRpcErrorDuplicate userInfo:nil];
}
// set the error on the op and cancels it so dependent ops can continue.
[op setHttpRpcError:error];
// Maybe enqueue the op
if (!error) {
[self addActiveRequest:op.netRequest.rpcRequest];
[self enqueueHTTPRequestOperation:op];
}
}
Il MY_AFHTTRequestOperation si costruisce l'istanza AFHTTPClient e dentro il successo e blocchi di completamento guasto chiamo [self removeActiveRequest:netRequest.rpcRequest];
come prima azione. Questi blocchi vengono eseguiti sul thread principale da AFNetworking come comportamento predefinito.
Ho visto il deadlock che si verifica quando l'ultimo blocco barriera che deve contenere il blocco in coda è sia il blocco add e il blocco di rimozione.
È possibile che quando il sistema genera più thread per supportare gli Ops CheckCacheAndFetchFromNetworkOp nel mio NSOperationQueue, il valore di activeRequestsQueue sarebbe troppo bassa per essere pianificato? Questo potrebbe causare un deadlock se tutti i thread sono stati presi dal blocco CheckCacheAndFetchFromNetworkOps per provare a leggere dal dizionario ActiveRequests e il parametro activeRequestsQueue stava bloccando un blocco di barriera di aggiunta/rimozione che non poteva essere eseguito.
UPDATE
Risolto il problema impostando il NSOperationQueue di avere conteggio maxConcurrentOperation di 1 (o realmente qualcosa di ragionevole diverso da quello predefinito NSOperationQueueDefaultMaxConcurrentOperationCount).
In pratica la lezione ho tolto è che non si dovrebbe avere un NSOperationQueue con il default Max numero di operazione di attesa su qualsiasi altro dispatch_queue_t o NSOperationQueue dal momento che potrebbe potenzialmente hog tutte le discussioni da quelle altre code.
Questo è ciò che stava accadendo.
coda - NSOperationQueue impostato su predefinito NSDefaultMaxOperationCount che consente al sistema di determinare il numero di operazioni simultanee da eseguire.
op - viene eseguito sulla coda1 e pianifica una richiesta di rete sulla coda AFNetworking dopo la lettura per assicurarsi che l'RPC non sia nel set di richiesta attiva.
Ecco il flusso:
Il sistema determina che può supportare 10 thread simultanei (In realtà era più come 80).
10 operazioni di pianificazione in una sola volta. Il sistema consente a 10 op di eseguire contemporaneamente sui suoi 10 thread. La chiamata di tutti i 10 operatori haActiveRequestForRPC che pianifica un blocco di sincronizzazione su activeRequestQueue e blocca i 10 thread. ActiveRequestQueue vuole eseguire il suo blocco di lettura, ma non ha thread disponibili. A questo punto abbiamo già un punto morto.
Più comunemente vedrei qualcosa come 9 ops (1-9) in programma, uno di loro, op1, esegue rapidamente un hasActiveRequestForRPC sul decimo thread e pianifica un blocco barrer addActiveRequest. Poi un altro op dovrebbe essere programmato sul 10 thread e l'op2-10 programmerebbe e attenderà un hasActiveRequestForRPC. Quindi il blocco addRpc programmato dell'op1 non sarebbe stato eseguito poiché l'op10 occupava l'ultimo thread disponibile e tutti gli altri blocchi hasActiveRequestForRpc aspettavano il blocco della barriera da eseguire. op1 finirebbe per bloccarsi in un secondo momento quando tentò di pianificare un'operazione di cache su una coda di operazioni diversa che non poteva accedere a nessun thread.
ero supponendo che il blocco hasActiveRequestForRPC erano in attesa su un blocco barrer da eseguire, ma la chiave era l'activeRequestQueue attesa sul qualsiasi disponibilità thread.
Il metodo getKey (dovresti chiamarlo chiave) protegge l'oggetto con un mutex? In questo caso, il problema è in activeRequestForRpc: il mutex è sempre bloccato e non termina. –
no non protegge l'oggetto con un mutex dato che i rpcs sono immutabili e non pensavo che fosse necessario. In realtà chiama semplicemente un altro metodo nella stessa classe che genera una chiave da un RPC, ma l'ho scritto in questo modo per semplicità.l'implementazione ha il seguente aspetto: methodSignature {return [NSSTringWithFormat, "stringa% @,% @ ..." rpc.someField ...]; } –
È possibile che questo metodo venga chiamato dalla stessa coda? assicurati di 'dispatch_get_current_queue! = activeRequestQueue' –