web-dev-qa-db-fra.com

Le thread NSMutableArray d'Objective-C est-il thread-safe?

J'essaie de réparer ce crash depuis presque une semaine. L'application se bloque sans exception ni trace de pile. L'application ne plante pas de quelque manière que ce soit lors de l'exécution d'instruments en mode zombie.

J'ai une méthode qui s'appelle sur un autre thread . La solution qui a corrigé le crash remplaçait

[self.mutableArray removeAllObjects];

avec 

dispatch_async(dispatch_get_main_queue(), ^{
    [self.searchResult removeAllObjects];
});

Je pensais que cela pouvait être un problème de synchronisation, alors j'ai essayé de le synchroniser, mais il s'est quand même écrasé:

@synchronized(self)
{
    [self.searchResult removeAllObjects];
}

Voici le code

- (void)populateItems
{
   // Cancel if already exists  
   [self.searchThread cancel];

   self.searchThread = [[NSThread alloc] initWithTarget:self
                                               selector:@selector(populateItemsinBackground)
                                                 object:nil];

    [self.searchThread start];
}


- (void)populateItemsinBackground
{
    @autoreleasepool
    {
        if ([[NSThread currentThread] isCancelled])
            [NSThread exit];

        [self.mutableArray removeAllObjects];

        // Populate data here into mutable array

        for (loop here)
        {
            if ([[NSThread currentThread] isCancelled])
                [NSThread exit];

            // Add items to mutableArray
        }
    }
}

Ce problème avec NSMutableArray n'est-il pas thread-safe?

48
aryaxt

Non.

Il n'est pas thread-safe et si vous avez besoin de modifier votre tableau mutable à partir d'un autre thread, utilisez NSLock pour vous assurer que tout se passe comme prévu:

NSLock *arrayLock = [[NSLock alloc] init];

[...] 

[arrayLock lock]; // NSMutableArray isn't thread-safe
[myMutableArray addObject:@"something"];
[myMutableArray removeObjectAtIndex:5];
[arrayLock unlock];
84
Daniel

Comme d'autres l'ont déjà dit, NSMutableArray n'est pas thread-safe. Au cas où quelqu'un voudrait obtenir plus que removeAllObject dans un environnement thread-safe, je donnerai une autre solution utilisant GCD en plus de celle utilisant lock. Ce que vous devez faire est de synchroniser les actions lire/mettre à jour (remplacer/supprimer).

Commencez par obtenir la file d'attente simultanée globale:

dispatch_queue_t concurrent_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

Pour lire:

- (id)objectAtIndex:(NSUInteger)index {
    __block id obj;
    dispatch_sync(self.concurrent_queue, ^{
        obj = [self.searchResult objectAtIndex:index];
    });
    return obj;
}

Pour insérer:

- (void)insertObject:(id)obj atIndex:(NSUInteger)index {
    dispatch_barrier_async(self.concurrent_queue, ^{
        [self.searchResult insertObject:obj atIndex:index];
    });
}

Dans Apple Doc à propos de dispatch_barrier_async:

Lorsque le bloc barrière atteint l'avant d'une file d'attente concurrente privée, il n'est pas exécuté immédiatement. Au lieu de cela, la file d'attente attend que ses blocs en cours d'exécution soient terminés. À ce stade, le bloc barrière s'exécute tout seul. Les blocs soumis après le bloc barrière ne sont pas exécutés tant que le bloc barrière n'est pas terminé.

Similaire pour enlever:

- (void)removeObjectAtIndex:(NSUInteger)index {
    dispatch_barrier_async(self.concurrent_queue, ^{
        [self.searchResult removeObjectAtIndex:index];
    });
}

EDIT: En fait, j'ai trouvé aujourd'hui un moyen plus simple de synchroniser l'accès à une ressource à l'aide d'une file d'attente série fournie par GCD.

De Apple Doc Guide de programmation simultanée> Files d'attente de répartition :

Les files d'attente en série sont utiles lorsque vous souhaitez que vos tâches soient exécutées dans un ordre spécifique. Une file d'attente série n'exécute qu'une tâche à la fois et les extrait toujours de la tête de la file. Vous pouvez utiliser une file d'attente série au lieu d'un verrou pour protéger une ressource partagée ou une structure de données modifiable. Contrairement à un verrou, une file d'attente série garantit que les tâches sont exécutées dans un ordre prévisible. Et tant que vous soumettez vos tâches à une file d'attente série de manière asynchrone, celle-ci ne peut jamais se bloquer.

Créez votre file d'attente série:

dispatch_queue_t myQueue = dispatch_queue_create("com.example.MyQueue", NULL);

Répartissez les tâches de manière asynchrone vers la file d'attente série:

dispatch_async(myQueue, ^{
    obj = [self.searchResult objectAtIndex:index];
});

dispatch_async(myQueue, ^{
    [self.searchResult removeObjectAtIndex:index];
});

J'espère que ça aide! 

36
Jingjie Zhan

De même que NSLock peut également utiliser @synchronized ( condition-objet ), il vous suffit de vous assurer que chaque accès du tableau est encapsulé dans un @synchronized avec le même objet agissant comme condition-objet , si vous le souhaitez. voulez seulement modifier le contenu de la même instance de tableau, vous pouvez utiliser le tableau lui-même comme condition-objet , sinon, vous devrez utiliser quelque chose d'autre que vous savez qui ne partira pas, l'objet parent, c'est-à-dire , est un bon choix car ce sera toujours le même pour le même tableau.

atomique dans les attributs @property rendra uniquement la sécurisation du thread de tableau sans modifier le contenu, c.-à-d. self.mutableArray = ... est thread-safe mais [self.mutableArray removeObject:] ne l’est pas.

19
Nathan Day
__weak typeof(self)weakSelf = self;

 @synchronized (weakSelf.mutableArray) {
     [weakSelf.mutableArray removeAllObjects];
 }

Cet article étonnant expliquera tout:

http://refactr.com/blog/2012/10/ios-tips-synchronized/

12
sash

Depuis les files d'attente en série ont été mentionnés: Avec un tableau mutable, il suffit de demander "est-ce qu'il est thread-safe" n'est pas suffisant. Par exemple, s'assurer que removeAllObjects ne se bloque pas est correct, mais si un autre thread tente de traiter le tableau en même temps, il traitera le tableau avant ou après tous les éléments. sont supprimés, et vous devez vraiment penser à ce que le comportement devrait être. 

La création d'un objet classe + responsable de ce tableau, la création d'une file d'attente série et l'exécution de toutes les opérations de la classe sur cette file d'attente constituent le moyen le plus simple d'améliorer la situation sans vous blesser le cerveau en cas de problèmes de synchronisation. 

5
gnasher729

L'objet de classes presque NSMutable n'est pas thread-safe.

1
Hardeep Singh

Toutes les classes NSMutablexxx ne sont pas thread-safe. Les opérations telles que get, insert, remove, add et replace doivent être utilisées avec NSLock. Voici une liste des classes thread-safe et thread-unsafe fournies par Apple: Résumé de sécurité des threads

0
holybiner