Threading et concurrence

77 views
Skip to first unread message

Mathias Kluba

unread,
Aug 9, 2012, 2:46:11 PM8/9/12
to alt.net group
Hello,

J'ai une petite question concernant la gestion de la concurrence.

J'ai un programme avec des threads "producer" et "consumer".
Je me sert d'une "blockingqueue" pour comminiquer.

au départ, j'arrivais à ajuster le nombre de producer/consumers pour que le consumer dépile la queue assez vite.
mais maintenant que je suis en prod, je suis sur un serveur mega puissant (avec beaucoup de core).
même si j'ai un seul thread producer, et le reste des cores pour le consumer, ils n'arrivent pas à dépiler.

le problème vient du fait qu'a un moment, le consumer doit voir s'il n'a pas traité un message (doublons).
pour ça, je fait un truc moche qui bloque tous les threads: un lock sur un HashSet.
j'essaye d'ajouter l'id du message dans le HashSet, si ça renvoit "true" c'est qu'il
n'existait pas déjà, false dans le cas contraire.

du coup, tous les threads bloquent pour vérifier si l'item n'est pas déjà dans la hashset...

avez vous des idées pour eviter ce lock?

une idée que j'ai c'est de "router" les messages dans des blocking queue différentes (1 par thread consumer) le routing se fait à l'aide d'un hash sur l'id du message. et chaque thread possède sa Hashset d'id déjà traité...
je garantie ansi que les doublons vont dans la même queue, que l'unicité est donc garantie, mais je répartie les locks par thread...

qui dit mieux?

--
Mathias Kluba
Twitter: @mathiaskluba
Blog: http://grozeille.com

Mathieu Despriee

unread,
Aug 9, 2012, 3:35:06 PM8/9/12
to paris...@googlegroups.com
Salut Mathias,

Idée : decorreler dédoublonnage et traitement.
Une thread de dedoublonnage (qui fait que ça) et qui remet le résultat
dans une autre Q pour les consumers.

Make sense ?

Mathieu

Mick Philippon

unread,
Aug 9, 2012, 3:42:16 PM8/9/12
to paris...@googlegroups.com
Ou alors tu fais porter l'info de traitement au message. En gros tu ajoutes à ton message deux booleens (volatiles) et une date : en cours de traitement, traité, date de début de traitement (et un object de synchro)
 
Tu as une queue avec tous les messages qui ne sont pas en cours de traitement et une avec les messages en cours de traitement (important pour la reprise sur erreur)
 
Ton début de traitement d'un message revient  à faire
var msg = queue.take(); // au pire tu as deux ou trois threads qui prennent le même message
lock(msg.__sync) // J'appelle toujours mes objets de synchro __sync, mais tu n'es pas obligé de faire pareil
{
  if(!msg.Processing && !msg.Processed)
  {
    msg.Processing = true;
    msg.StartProcessingDate = DateTime.Now; // à voir si la précision est suffisante
    processingQueue.Put(msg);
    process (msg);
  }
}

Quand ta queue de départ est vide, tu vas chercher dans la processingQueue les message qui ne sont pas déjà traités et dont la date de début est antérieure à x minutes, histoire de pallier aux défaillance de tes consumers.
 
Avantage : tu lockes au niveau du message seulement, donc dans le pire des cas tu vas bloquer deux ou trois thread (ceux qui ont fait le take en même temps du même message)
 
Mick, distribué.

mathias kluba

unread,
Aug 9, 2012, 4:14:50 PM8/9/12
to paris...@googlegroups.com
Tout d'abord, j'ai plusieurs queues.
Donc je décolère déjà traitement et "détection des doublons".
Avant je n'avais pas de problème car les traitements prenaient pas mal de CPU.
Mais quand je suis passé en prod avec un serveur de ouf, les traitements vont trop vite et tout bloque sur la détection des doublons.

message => queue 1 => traitements  A => queue 2 => détection des doublons => queue 3 => traitements B

le problème c'est que le "traitements A" va tellement vite, que la queue 2 est pleine (capé sinon ça explose en mémoire).
et la queue3 est vide, car la "détection des doublons" va pas assez vite pour dépiler la queue 2, et pas assez vite pour remplir la queue 3.


Concernant le lock au niveau du message: mes threads ne peuvent pas dépiler le même message (blockingQueue).
J'ai des doublons au niveau de la source des messages.




désolé de ne pas avoir été assez détaillé dans mon premier message, j'étais sur iPhone dans le metro :p

PS: je sais que ma solution de "blocking queue" n'est pas idéal: je pourrais déjà utiliser TPL Dataflow, voir même le Disruptor.

Mathieu Despriee

unread,
Aug 9, 2012, 4:25:16 PM8/9/12
to paris...@googlegroups.com
Peut-être qu'il y a un truc que je pige pas, mais : je suppose que si le traitement A est rapide, il n'est que sur une thread. Est-ce que ça peut pas être lui qui a la hashmap et qui dedoublonne ?

mathias kluba

unread,
Aug 9, 2012, 4:45:35 PM8/9/12
to paris...@googlegroups.com
Arf, non, car la traitement A génère le messages à partir d'un fichier texte, et génère l'ID aussi.
Même si je colle la Hashmap dans ce thread, ça ira pas plus vite.
L'intérêt c'est que je puisse avoir plusieurs threads pour le traitement A, pour traiter encore plus de fichiers (ce qui ne sert à rien pour l'instant car ça bloque au dédoublonnage)

2012/8/9 Mathieu Despriee <mdes...@octo.com>

Mick Philippon

unread,
Aug 10, 2012, 4:16:59 AM8/10/12
to paris...@googlegroups.com
Question :: Est-ce une catastrophe si un message est traité en double de temps à autre où pas ?

Si ton objectif est simplement d'économiser du temps de calcul, tu peux ne locker qu'en écriture (ou au besoin utiliser un ReaderWriterSlimLock). Certains messages seront traités en double, mais bon, c'est pas la mort.
Si en revanche traiter deux fois le même message relève de la catastrophe nucléaire, il va falloir ruser un peu plus. Il existe un truc plus rapide que le hashset pour déterminer les doublons*. Je ne retrouve plus le nom, mais en gros c'est un champ de bits dans lequel on insère des infos. C'est destructeur (cad que tu ne peux retrouver un truc inséré)) mais ça permet de tester l'existence avec un simple ou binaire. J'essaie de te retrouver le nom du truc, sauf si quelqu'un le connait de tête.

* du moins sur un hashset qui évolue, vu que sur un hashset fixe, ta complexité tend vers O(1)

Mick

Mick Philippon

unread,
Aug 10, 2012, 4:18:34 AM8/10/12
to paris...@googlegroups.com
Trouvé ! ça s'appelle un filtre de Bloom : http://blog.octo.com/le-filtre-de-bloom/

Mick, google est mon ami.

Mathieu Despriee

unread,
Aug 10, 2012, 4:57:23 AM8/10/12
to paris...@googlegroups.com
Je pensais aussi au filtre de Bloom, mais le pb c'est qu'il peut te donner des faux positifs. C'a-d te dire qu'il y a un doublon, alors que c'est faux. Du coup tu perds des messages (et au contraire, pas de traitement en double). 


Autre idée : N dédoublonneurs en parallèle.  Tu envoies ton message M dans la file p=Hash(M) mod N. Et tu es sûr que tous les doublons de M seront dans la même file. Du coup, plus de lock. 
Me gourre-je ?


Le 10 août 2012 10:16, Mick Philippon <mphil...@octo.com> a écrit :

Mathias Kluba

unread,
Aug 10, 2012, 1:08:23 PM8/10/12
to paris...@googlegroups.com
hello,

bonne idée pour le readWriteLock, je l'utilise déjà (mais j'ai beaucoup de doublons donc beaucoup de lock finalement)


ce n'est pas une catastrophe si j'ai des doublons, c'étais pour éviter de bourriner la base de donnée en sortie (mongo pour les curieux)

par contre, c'est catastrophique d'en oublier, donc je ne vois pas comment utiliser le filtre bloom dans ce cas.

par contre, je n'ai pas besoin parcourir le Hashset, ca peut donc etre une solution "destructrice"

concernant le Hash(m) mod N, c'est exactement l'idée de mon tout premier message, et c'est ce que j'ai fait.
inconvénient: le "getHashcode" n'est pas forcement la meilleur solution car il peut mal repartir les messages: je peux avoir 1 queue de 1000messages avec un thread qui carbure, et une queue vide avec un thread qui attend...
mais dans mon cas ça à l'air de globalement bien repartir

thx en tout cas pour le bloomfilter, on y pense rarement ;)


autour de ce sujet: qui utilise vraiment TPL Dataflow ? (en dehors des hello world?)
--
Mathias Kluba
Twitter: @mathiaskluba
Blog: http://grozeille.com

Yann Schwartz

unread,
Aug 10, 2012, 3:36:00 PM8/10/12
to paris...@googlegroups.com
Oui mais le filtre de bloom va devoir être mis à jour, donc on se retrouve avec k calculs de hash, le tout locké, ce qui est pas forcément plus intéressant.

Sinon utiliser un concurrent dictionary, qui est lock-free et propose de meilleures performances.


2012/8/10 Mathieu Despriee <mdes...@octo.com>

Mathias Kluba

unread,
Aug 11, 2012, 12:30:16 PM8/11/12
to paris...@googlegroups.com
Hello,

J'avais même développé mon ConcurrentDico car je ne peux pas faire en .Net 4.0 (j'utilisais alors le ReadWriteLock).
Finalement, j'ai trouvé une version de la TPL pour .Net < 4.0.
Je ne vois d'ailleurs pas l'intérêt à forcer les gens à faire du .Net 4 pour ça… la TPL existait avant…

Mais si je fait:

if(!dico.TryGetValue(key, out fakeObject))
{
  dico.Add(key, null);
}

J'ai pas ce que je veux: entre le moment ou je test si l'élément est présent, et le moment ou je l'ajoute, un autre thread peux être plus rapide…
Je peux aussi ajouter un catch quand ça arrive…

Le HashSet me plait dans le sens ou il une méthode "Add" qui renvoie "true/false" si l'élément existe déjà. Je lock que cette instruction (même si je lock aussi tout ce que fait la méthode Add..)

Est-ce que je me trompe? est-ce qu'il y a une autre façon d'utiliser le concurrentDico?

-- 
Mathias Kluba
Twitter: @mathiaskluba

Jb Evain

unread,
Aug 11, 2012, 12:37:22 PM8/11/12
to paris...@googlegroups.com
Ça dépend de ce que tu veux faire concrètement, mais t'as TryAdd et GetOrAdd sur le ConcurrentDico.

Mathias Kluba

unread,
Aug 11, 2012, 12:50:59 PM8/11/12
to paris...@googlegroups.com
Thx, je suis passé à coté :) c'est exactement ce qu'il me faut.
Ceci dit, j'ai déjà opté pour la solution d'une queue par thread avec une répartition avec HashCode modulo NbQueue, mais c'est bon à savoir.
Je vais de ce pas regardé comment ils ont implémenté ça ;)

En parlant de concurrence, quelqu'un a essayé http://code.google.com/p/disruptor-net/ ?

-- 
Mathias Kluba
Twitter: @mathiaskluba
Reply all
Reply to author
Forward
0 new messages