[Obj-c] Lazy-loading et thread-safe mutable dictionary
colas_
Membre
Bonjour,
est-ce que ce code est bon?
Il est censé créer des entrées dans un dictionnaire de façon lazy ET thread-safe. Je n'ai jamais utiliser @synchronized donc c'est pour savoir.
Merci!
@property (nonatomic, readwrite, strong) NSMutableDictionary * mutableDictionary;
[...]
- (id)objectForKey:(NSString *)key
{
id result = self.mutableDictionary[key];
if (!result)
{
@synchronized(self)
{
id result = self.mutableDictionary[key];
if (!result)
{
result = [...] ; // go and fetch the result;
self.mutableDictionary[key] = result;
}
}
}
return result ;
}
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Je dirais oui et non.
Formellement oui.
Le double accès au dictionnaire est un peu bizarre au premier abord. Je crois comprendre que c'est pour améliorer les performances : ne pas verrouiller de mutex si la clé est trouvée.
Le lazy load à l'intérieur d'un bloc protégé me paraà®t dangereux. S'il y a lazy load c'est que l'opération est longue, voire un résultat incertain, et du coup je pense que c'est à éviter dans un bloc protégé.
Tu pourrais très bien avoir un thread qui crée la clé pendant qu'un autre est en train de la tester, on ne sait pas trop ce qui va se passer dans le code de NSMutableDictionary à ce moment là .
Ton code serait correct si le valueForKey était une opération atomique.
D'autre part il est dommage de mettre le fetch dans le synchronized car cela va bloquer les autres thread alors qu'il n'y a pas de menace sur les donnees partagees pendant ce fetch.
J'ai supposé que le dictionnaire était forcément mis à jour dans cette méthode et jamais ailleurs, j'ai peut-être mal supposé.
@jpimbert tu as bien supposé !
Pour répondre à ta question
Le double-test c'est pour la raison suivante : si deux threads entrent au même moment dans la méthode et que le result est nil. Un seul rentrera dans le @synchronized (c'est du moins ce que j'imagine que fait @synchronized). Celui qui rentre va créer l'entrée du dico et l'autre attend. Quand finalement l'autre entre dans le @synchronized, il ne faut pas qu'il recrée l'entrée du dico. Donc, je re teste mais cette fois-ci, l'entrée n'est pas nil, car elle vient d'être crée par l'autre thread.
@KDEV
Je ne suis pas d'accord avec toi (mais je m'y connais mal, donc si je me trompe dis-moi)
Dans ce pattern, la valeur du dico est fixée une fois pour toutes. Si thread1 lit valeurDico alors que thread2 écrit cette valeur, cela veut dire que thread2 est dans le @synchronized. Deux cas possibles :
-> Si thread1 lit une valeur non-nil de valeurDico, c'est ok, c'est la bonne valeur.
-> Si thread1 lit nil, il va entrer dans le @synchronized. Mais il doit attendre que thread2 sorte de @synchronized. Or, à ce moment, la valeurDico sera non-nil et donc thread1 renverra la bonne valeur.
Si thread2 est en train d'écrire la valeur du dico, pendant que thread1 essaye de la lire, on a potentiellement le cas suivant :
thread2 est dans la méthode [NSMutableDictionary setValue:ForKey:]
thread1 est dans la méthode [NSMutableDictionary value:forKey:]
A partir de là , NSMutableDictionary n'étant pas thread safe (à vérifier), on ne sait pas ce qui peut arriver aux états internes du NSMutableDictionary.
Formellement le double test ne sert à rien. Le code suivant marche tout aussi bien.
La seule différence est que chaque appel à objectForKey va verrouiller le mutex, ce qui fait un peu perdre en performance.
@jpimber
Oui, le seul cas où l'on doit gérer le multithread est si on doit créer l'entrée du dico.
NSMutableDictionary n'est pas thread-safe (avec la déf de thread-safe qui dirait que si thread1 lit une valeur du dico, cette valeur sera la même s'il la relit plus tard).
Mais, IMHO, on peut lire et écrire en même temps dans NSMutableDico (dans ce sens-là , il est thread safe). En fait, je n'en sais rien mais j'imagine que c'est comme ça !
Je devrais laisser Ali répondre à ça, mais j'ai pitié des chats.
Sur cette page, on peut lire.
Bon ça parle surtout de changements simultanés mais à la fin du paragraphe, il est bien dit de protéger les lectures aussi.
C'est logique car lors d'une écriture, les listes chaà®nées ou hash table ou autre pointeurs utilisés en interne de la classe mutable peuvent être chamboulés. Si une lecture simultanée utilise ces mêmes pointeurs alors elle risque de se retrouver le bec dans l'eau.
Hello,
@Colas j'ai trouvé ça, ça pourrais t'intéresse !
https://gist.github.com/steipete/5928916
Ca ne change rien à l'affirmation de FKDEV avec laquelle je suis totalement d'accord. Par contre ta solution bien que plus coûteuse, est correcte.
On peut imaginer le cas suivant (avec le code de Colas2) :
Dans cette situation le comportement est totalement imprévisible, car NSDictionnary n'est pas thread safe à partir du moment où il y a une écriture (2 threads peuvent accéder à un NSDictionnary en lecture en // car dans ce cas il est thread safe).
Bon sinon il existe un excellent article de Mike Ash qui explique comment gérer correctement un cache d'objets à l'aide de NSMutableDictionnary et GCD pour pallier à sa "non thread-safitude" : https://mikeash.com/pyblog/friday-qa-2011-10-14-whats-new-in-gcd.html
Superb article. Le GCD et bien la solution la préférée.