web-dev-qa-db-fra.com

Question d'entretien de synchronisation multithreading: Trouver n mots donnés m fils

Existe-t-il un moyen pour ce problème de bénéficier d'une solution avec plusieurs threads plutôt qu'un seul thread?


Dans une interview, on m'a demandé de résoudre un problème en utilisant plusieurs threads. Il me semble que les multiples threads ne présentent aucun avantage.

Voici le problème:

On vous donne un paragraphe, qui contient n nombre de mots, on vous donne m fils. Ce que vous devez faire, c'est que chaque thread doit imprimer un mot et donner le contrôle au thread suivant, de cette façon, chaque thread continuera à imprimer un mot, au cas où le dernier thread arriverait, il devrait invoquer le premier thread. L'impression se répétera jusqu'à ce que tous les mots soient imprimés dans le paragraphe. Enfin, tous les threads devraient se terminer correctement. Quel type de synchronisation utilisera?

Je pense fermement que nous ne pouvons pas tirer parti des discussions ici, mais je crois que l'intervieweur essaie de mesurer mes compétences en synchronisation. Suis-je en train de manquer quelque chose dans ce problème qui donnerait de la valeur à plusieurs threads?

Pas besoin de code, mettez juste quelques réflexions. Je mettrai en œuvre par moi-même.

23
rplusg

Il me semble qu'ils vous conduisent vers une solution de sémaphore. Les sémaphores sont utilisés pour signaler à un autre thread que c'est leur tour. Ils sont utilisés beaucoup moins fréquemment que les mutex, ce qui explique pourquoi je pense que c'est une bonne question d'entrevue. C'est aussi pourquoi l'exemple semble artificiel.

Fondamentalement, vous créez des sémaphores m. Chaque thread x attend le sémaphore x puis publie sur le sémaphore x+1 après avoir fait son truc. En pseudocode:

loop:
    wait(semaphore[x])
    if no more words:
        post(semaphore[(x+1) % m])
        exit
    print Word
    increment current Word pointer
    post(semaphore[(x+1) % m])
21
Karl Bielefeldt

À mon avis, il s'agit d'une fabuleuse question d'entrevue - au moins en supposant (1) que le candidat devrait avoir une connaissance approfondie du filetage, et (2) l'intervieweur possède également une connaissance approfondie et utilise la question pour sonder le candidat. Il est toujours possible que l'enquêteur recherche une réponse précise et précise, mais un enquêteur compétent doit rechercher les éléments suivants:

  • Capacité à différencier les concepts abstraits de la mise en œuvre concrète. Je jette celui-ci principalement comme un méta-commentaire sur certains des commentaires. Non, cela n'a aucun sens de traiter une seule liste de mots de cette façon. Cependant, le concept abstrait d'un pipeline d'opérations, qui peut s'étendre sur plusieurs machines de capacités différentes, est important.
  • D'après mon expérience (près de 30 ans d'applications distribuées, multi-processus et multi-thread), la distribution du travail n'est pas la partie difficile. Rassembler les résultats et coordonner les processus indépendants sont les endroits où la plupart des bogues de thread se produisent (encore une fois, selon mon expérience). En distillant le problème jusqu'à une chaîne simple, l'intervieweur peut voir dans quelle mesure le candidat pense à la coordination. De plus, l'intervieweur a la possibilité de poser toutes sortes de questions complémentaires, telles que "OK, que se passe-t-il si chaque thread doit envoyer son Word à un autre thread pour la reconstruction."
  • Le candidat pense-t-il à la façon dont le modèle de mémoire du processeur pourrait affecter la mise en œuvre? Si les résultats d'une opération ne sont jamais vidés du cache L1, c'est un bogue même s'il n'y a pas de simultanéité apparente.
  • Le candidat sépare-t-il le filetage de la logique d'application?

Ce dernier point est, à mon avis, le plus important. Encore une fois, d'après mon expérience, il devient exponentiellement plus difficile de déboguer du code threadé si le threading est mélangé avec la logique d'application (regardez simplement toutes les questions Swing sur SO pour des exemples). I estiment que le meilleur code multi-thread est écrit en tant que code mono-thread autonome, avec des transferts clairement définis.

Dans cet esprit, mon approche serait de donner à chaque thread deux files d'attente: une pour l'entrée, une pour la sortie. Le thread se bloque lors de la lecture de la file d'attente d'entrée, enlève le premier mot de la chaîne et transmet le reste de la chaîne à sa file d'attente de sortie. Certaines des caractéristiques de cette approche:

  • Le code d'application est responsable de la lecture d'une file d'attente, de la modification des données et de l'écriture de la file d'attente. Peu importe si elle est multithread ou non, ou si la file d'attente est une file d'attente en mémoire sur une machine ou une file d'attente basée sur TCP entre des machines qui vivent sur des côtés opposés du monde.
  • Parce que le code d'application est écrit comme un seul thread, il peut être testé de manière déterministe sans avoir besoin de beaucoup d'échafaudage.
  • Pendant sa phase d'exécution, le code d'application possède la chaîne en cours de traitement. Il n'a pas à se soucier de la synchronisation avec les threads exécutés simultanément.

Cela dit, il y a encore beaucoup de zones grises qu'un enquêteur compétent peut sonder:

  • "D'accord, mais nous cherchons à connaître votre connaissance des primitives de concurrence. Pouvez-vous implémenter une file d'attente de blocage?" Votre première réponse, bien sûr, devrait être que vous utilisiez une file d'attente de blocage prédéfinie à partir de la plate-forme de votre choix. Cependant, si vous comprenez les threads, vous pouvez créer une implémentation de file d'attente dans moins d'une douzaine de lignes de code, en utilisant les primitives de synchronisation prises en charge par votre plateforme.
  • "Et si une étape du processus prend très longtemps?" Vous devez vous demander si vous souhaitez une file d'attente de sortie limitée ou illimitée, comment vous pouvez gérer les erreurs et les effets sur le débit global en cas de retard.
  • Comment mettre efficacement en file d'attente la chaîne source. Ce n'est pas nécessairement un problème si vous avez affaire à des files d'attente en mémoire, mais cela pourrait être un problème si vous passez d'une machine à l'autre. Vous pouvez également explorer des wrappers en lecture seule au-dessus d'un tableau d'octets immuable sous-jacent.

Enfin, si vous avez de l'expérience en programmation concurrente, vous pourriez parler de certains cadres (par exemple, Akka pour Java/Scala) qui suivent déjà ce modèle.

22
kdgregory

Les questions d'entrevue sont parfois en fait des questions pièges, destinées à vous faire réfléchir sur le problème que vous essayez de résoudre. Poser des questions sur une question fait partie intégrale de l'approche tout d'un problème, que ce soit dans le monde réel ou dans une interview. Il existe un certain nombre de vidéos circulant sur Internet sur la façon d'aborder les questions dans les entretiens techniques (recherchez en particulier Google et peut-être Microsoft).

"Essayez juste de répondre, et sortez de là ..."

Approcher les entretiens avec ce schéma de pensée vous conduira à bombarder n'importe quel entretien pour toute entreprise qui vaut la peine de travailler.

Si vous ne pensez pas que vous gagnez beaucoup (si quelque chose de filetage), dites-le-leur. Dites-leur pourquoi vous ne pensez pas qu'il y ait un avantage. Discutez avec eux. Les entretiens techniques sont censés être une plateforme de discussion ouverte. Vous pourriez finir par apprendre quelque chose sur la façon dont cela peut être utile. Ne vous contentez pas d'aller de l'avant aveuglément en essayant de mettre en œuvre quelque chose que votre intervieweur vous a dit de faire.

16
Demian Brecht

Comme vous l'avez dit, je ne pense pas que ce scénario profite grandement, voire pas du tout, du threading. C'est probablement plus lent qu'une implémentation à un seul thread.

Cependant, ma réponse serait d'avoir chaque thread dans une boucle serrée essayant d'accéder à un verrou qui contrôle l'accès à l'index du tableau Word. Chaque thread saisit le verrou, obtient l'index, obtient le mot correspondant du tableau, l'imprime, incrémente l'index puis libère le verrou. Le thread se ferme lorsque l'index se trouve à la fin du tableau.

Quelque chose comme ça:

while(true)
{
    lock(index)
    {
        if(index >= array.length())
          break;
        Console.WriteLine(array[index]);
        index++;
    }
}

Je pense que cela devrait atteindre l'un thread après une autre exigence, mais l'ordre des threads n'est pas garanti. Je suis curieux d'entendre aussi d'autres solutions.

0
ConditionRacer
  • Commencez par symboliser le paragraphe avec les délimiteurs appropriés et ajoutez les mots à une file d'attente.

  • Créez N nombre de threads et conservez-le dans un pool de threads.

  • Itérer sur le pool de threads et démarrer le thread et attendre la
    thread à rejoindre. Et commencez le fil suivant une fois le premier fil terminé et ainsi de suite.

  • Chaque thread doit simplement interroger la file d'attente et l'imprimer.

  • Une fois que tous les threads sont utilisés dans le pool de threads, commencez par le début du pool.

0
java_mouse