8
votes

Fil de consommation efficace avec plusieurs producteurs

J'essaie de faire une situation de producteur / de culture de consommation plus efficace en sautant des opérations d'événement coûteuses si nécessaire avec quelque chose comme:

//cas(variable, compare, set) is atomic compare and swap
//queue is already lock free

running = false


// dd item to queue – producer thread(s)

if(cas(running, false, true))
{
  // We effectively obtained a lock on signalling the event
  add_to_queue()
  signal_event()
}
else
{
  // Most of the time if things are busy we should not be signalling the event
  add_to_queue()

  if(cas(running, false, true))
    signal_event()
}

...

// Process queue, single consumer thread

reset_event()

while(1)
{
  wait_for_auto_reset_event() // Preferably IOCP

  for(int i = 0; i &lt SpinCount; ++i)
    process_queue()

  cas(running, true, false)

  if(queue_not_empty())
    if(cas(running, false, true))
      signal_event()
}


3 commentaires

Dans quelle langue votre code est-il votre code?


Les opérations d'événements sont-elles coûteuses ou le traitement du fil de consommation coûtent-ils également cher? (Et vous voulez éviter cela aussi?)


Désolé, j'ai juste remarqué que tous ces commentaires maintenant! C'est en C ++, bien que je suis intéressé à répliqué similaire à Csharp. Une opération de synchronisation des événements est la chose la plus chère si elle est prise pour chaque élément de la file d'attente.


4 Réponses :


0
votes

est allé à travers un tas de cas, je ne peux pas voir un problème. Mais c'est un peu compliqué. Je pensais que vous auriez peut-être un problème avec Queue_Not_empty / add_to_queue Racing. Mais on dirait que le CAS post-dominant dans les deux chemins couvre ce cas.

CAS est coûteux (pas aussi cher que le signal). Si vous vous attendez à sauter le signal d'être courant, je coderais le CAS comme suit: P>

running = false 

// add item to queue – producer thread(s) 
add_to_queue()
if (cas(running, false, true)) {
   signal_event()
}

// Process queue, single consumer thread 

reset_event() 

while(1) 
{ 
    wait_for_auto_reset_event() // Preferably IOCP 

   for(int i = 0; i &lt SpinCount; ++i) 
       process_queue() 

   cas(running, true, false)  // this could just be a memory barriered store of false

   if(queue_not_empty()) 
      if(cas(running, false, true)) 
         signal_event() 
} 


2 commentaires

Désolé j'ai manqué tous ces commentaires jusqu'à présent - merci pour le vôtre! - Et je prends juste le temps de digérer ce que vous avez dit maintenant ...


Sur la première partie de votre commentaire (comme je commence à me souvenir de mon train de réflexion d'avant!) - Oui, je l'ai structuré autour de cette façon (je pense!) Pour faire face à une condition de course que je me souviens d'être causée dans un exemple comme La réponse d'Anthony. Je pense que j'allais pour le moindre des maux - en demandant au travailleur dans certains cas, boucle peut-être plus de fois que nécessaire au pire des cas.Je digestion de votre suggestion plus maintenant et votre commentaire sur le magasin de mémoire de mémoire que je me demandais auparavant Quelque chose comme ça, mais décider d'essayer de le faire travailler avec des choses verrouillées d'abord :)



0
votes

Pourquoi ne pas simplement associer un bool avec l'événement? Utilisez CAS pour le définir sur true, et si le CAS réussit alors signalez l'événement car l'événement doit avoir été clair. Le serveur peut alors simplement effacer le drapeau avant d'éviter xxx

de cette façon, vous n'attendez que s'il n'y a pas d'éléments sur la file d'attente et que vous signalez uniquement l'événement une fois pour chaque lot d'éléments.


1 commentaires

Désolé ignore mon dernier commentaire que j'ai frappé le retour trop vite! - Je pense que j'ai essayé quelque chose comme ça, mais j'ai travaillé là-bas, il y avait une condition de course avec elle entre le bit d'éclair et la file d'attente étant vide.



0
votes

Je crois, vous voulez réaliser quelque chose comme dans cette question: de
WinForms multithreading: exécuter une interface graphique Mise à jour uniquement si le précédent est terminé. Il est spécifique sur C # et Winforms, mais la structure peut bien s'appliquer à vous.


0 commentaires

2
votes

Cela tombe dans la sous-catégorie de "Arrêtez de jouer et retourner au travail" appelé "Optimisation prématurée". : -)

Si les opérations d'événement "coûteuses" occupent une partie de temps importante, votre conception est fausse et plutôt que d'utiliser un producteur / consommateur, vous devez utiliser une section critique / mutex et faire le travail de la Appelant le fil.

Je vous suggère de profil de votre application si vous êtes vraiment concerné.

Mise à jour:

:

producteur xxx

consommateur xxx

notes:

  • L'événement est un événement de réinitialisation manuelle.
  • Les opérations protégées par la section critique sont rapides. L'événement est uniquement défini ou réinitialisé lorsque la file d'attente transitions vers / depuis l'état vide. Il doit être défini / réinitialiser dans la section critique pour éviter une condition de race.
  • Cela signifie que la section critique n'est conservée que pendant une courte période. alors la conflit sera rare.
  • Les sections critiques ne bloquent pas à moins qu'ils ne soient affirmés. Alors les commutateurs contextuels seront rares.

    hypothèses:

    • C'est un vrai problème, pas les devoirs.
    • Les producteurs et les consommateurs passent la majeure partie de leur temps à faire d'autres choses, c'est-à-dire d'obtenir les articles prêts à la file d'attente, de les traiter après la retrait de la file d'attente.
    • S'ils passent la plupart du temps à faire les opérations de la file d'attente, vous ne devriez pas utiliser une file d'attente. J'espère que c'est évident.

3 commentaires

Je suis désolé mais c'est une réponse assez mauvaise, même si j'apprécie que vous essayez d'aider. Les sections critiques et les mutiles sont trop lentes pour le type de débit que je travaille. Beaucoup de celles-ci provient des commutateurs contextuels du système d'exploitation pouvant arriver.


@WB, vous réinventez simplement la section critique. C'est précisément que les sections critiques de problèmes sont conçues pour résoudre, alors utilisez-les simplement. Ils ne bloquent pas à moins d'avoir une affirmation


Pour être clair, votre solution proposée consiste à utiliser une barrière de mémoire / comparer et échanger et échanger des opérations pour éviter de signaler ou d'attendre un objet / un sémaphore d'événement. C'est exactement ce que font les sections critiques et votre exemple est exactement la raison pour laquelle ils le font. C'est pourquoi la réponse à votre question est "Utiliser une section critique" - pas parce que quelque chose ne va pas avec votre idée, mais parce que cela a déjà été inventé.