web-dev-qa-db-fra.com

besoin de clarifications sur la file d'attente de répartition, le thread et NSRunLoop

Les choses suivantes sont ce que je sais et comprends:

La file d'attente globale est une file d'attente simultanée qui peut répartir les tâches sur plusieurs threads. L'ordre d'exécution de la tâche n'est pas garanti. par exemple.:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), {
 for (int i; i<10; i++) {
  doTask()
 }
})

Si je veux envoyer dans la file d'attente série, je peux utiliser

dispatch_async(dispatch_queue_create("my.serial.queue", nil) {
  ...
}

chaque fois qu'une seule tâche est envoyée à un thread et exécutée. La commande est FIFO.

===== Ce que je suis confus et que je ne comprends pas complètement =======

  1. Le thread principal a un NSRunLoop, des tâches de boucle dans le thread principal. Je me demande quelle est la relation entre la file d'attente de répartition et la boucle d'exécution? Puis-je le comprendre comme, si l'envoi d'une tâche au thread principal, NSRunLoop du thread principal obtient la tâche distribuée et l'exécute?

  2. Qu'en est-il de la file d'attente globale qui répartit les tâches sur plusieurs threads? Le système iOS/OSX crée-t-il automatiquement non seulement les threads, mais crée-t-il également NSRunLoop pour chaque thread? puis la boucle d'exécution dans chaque thread obtient la tâche distribuée de la file d'attente globale et l'exécute?

  3. Qui connaît le fil? La fonction dispatch_async() et dispatch_sync() sait-elle à quel thread envoyer la tâche ou la queue sait-elle à quel thread envoyer la tâche?

  4. Existe-t-il un moyen d'obtenir l'objet NSRunLoop du thread (auquel la tâche est envoyée) à partir de la file d'attente de répartition par programme? (cette question est liée à la question 3)

23
Leem.fin
  1. La boucle d'exécution du thread principal comporte une étape au cours de laquelle il exécute tous les blocs mis en file d'attente dans la file d'attente principale. Vous pourriez trouver cette réponse utile si vous voulez comprendre en détail ce que fait la boucle d'exécution.

  2. GCD crée les threads pour les files d'attente simultanées. Un thread n'a pas de boucle d'exécution jusqu'à ce que la première fois que quelque chose s'exécutant sur le thread demande la boucle d'exécution du thread, à quel point le système crée une boucle d'exécution pour le thread. Cependant, la boucle d'exécution ne s'exécute que si quelque chose sur ce thread lui demande de s'exécuter (en appelant -[NSRunLoop run] ou CFRunLoopRun ou similaire). La plupart des threads, y compris les threads créés pour les files d'attente GCD, n'ont jamais de boucle d'exécution.

  3. GCD gère un pool de threads et, lorsqu'il doit exécuter un bloc (car il a été ajouté à une file d'attente), GCD sélectionne le thread sur lequel exécuter le bloc. L'algorithme de sélection de threads de GCD est principalement un détail d'implémentation, sauf qu'il choisit toujours le thread principal d'un bloc qui a été ajouté à la file d'attente principale. (Notez que GCD utilisera parfois le thread principal pour un bloc ajouté à une autre file d'attente.)

  4. Vous ne pouvez obtenir que la boucle d'exécution du thread principal (en utilisant +[NSRunLoop mainRunLoop] ou CFRunLoopGetMain) ou la boucle d'exécution du thread courant (en utilisant +[NSRunLoop currentRunLoop] ou CFRunLoopGetCurrent). Si vous avez besoin de la boucle d'exécution d'un thread arbitraire, vous devez trouver un moyen d'appeler CFRunLoopGetCurrent sur ce thread et de renvoyer sa valeur de retour sur les threads de manière sécurisée et synchronisée.

    Veuillez noter que l'interface NSRunLoop n'est pas thread-safe, mais l'interface CFRunLoop est sûr pour les threads, donc si vous avez besoin d'accéder à la boucle d'exécution d'un autre thread, vous devez utiliser l'interface CFRunLoop.

    Notez également que vous ne devriez probablement pas exécuter une boucle d'exécution très longtemps dans un bloc exécuté sur une file d'attente GCD, car vous bloquez un thread que GCD s'attend à contrôler. Si vous devez exécuter une boucle d'exécution pendant une longue période, vous devez démarrer votre propre thread pour cela. Vous pouvez voir un exemple de cela dans le _legacyStreamRunLoop fonction dans CFStream.c . Notez comment il rend la boucle d'exécution du thread dédié disponible dans une variable statique nommée sLegacyRL, qu'il initialise sous la protection d'un dispatch_semaphore_t.

28
rob mayoff
  1. La relation entre la boucle d'exécution du thread principal et la file d'attente de répartition principale est simplement qu'elles sont toutes deux exécutées sur le thread principal et que les blocs distribués dans la file d'attente principale sont entrelacés sur le thread principal avec des événements traités sur la boucle d'exécution principale.

    Comme le dit Guide de programmation des accès concurrents :

    La file d'attente de répartition principale est une file d'attente série disponible à l'échelle mondiale qui exécute des tâches sur le thread principal de l'application. Cette file d'attente fonctionne avec la boucle d'exécution de l'application (le cas échéant) pour entrelacer l'exécution des tâches en file d'attente avec l'exécution d'autres sources d'événements attachées à la boucle d'exécution. Parce qu'elle s'exécute sur le thread principal de votre application, la file d'attente principale est souvent utilisée comme point de synchronisation clé pour une application.

  2. Lors de la répartition vers un thread d'arrière-plan, il ne crée pas un NSRunLoop pour ces threads de travail. Vous n'avez généralement pas non plus besoin d'une boucle d'exécution pour ces threads d'arrière-plan. Auparavant, nous devions créer notre propre NSRunLoop pour les threads d'arrière-plan (par exemple lors de la planification de NSURLConnection sur le thread d'arrière-plan), mais ce modèle n'est plus requis très souvent.

    Pour les choses nécessitant historiquement des boucles d'exécution, il existe souvent de meilleurs mécanismes si vous les exécutez sur un thread d'arrière-plan. Par exemple, au lieu de NSURLConnection, vous utiliseriez maintenant NSURLSession. Ou, plutôt que NSTimer sur NSRunLoop sur le thread d'arrière-plan, vous créeriez une source de répartition du temporisateur GCD.

  3. En ce qui concerne qui "connaît" le thread, le thread de travail est identifié lorsqu'il est envoyé dans une file d'attente. Le thread n'est pas une propriété de la file d'attente, mais plutôt affecté à la file d'attente lorsque la file d'attente en a besoin.

  4. Si vous voulez créer un NSRunLoop pour un thread de travail (ce que vous ne devriez généralement pas faire, de toute façon), vous le créez et vous gardez une trace de vous-même. Et, lors de la planification d'un thread avec une boucle d'exécution, je serais enclin à créer le NSThread moi-même et à planifier la boucle d'exécution à ce sujet, plutôt que de lier l'un des threads de travail de GCD.

5
Rob