OVH Cloud OVH Cloud

Thread et condition (pas taper)

16 réponses
Avatar
Bruno CAUSSE
Bonsoir,

Je sais que le groupe n'est pas le plus adapté (et encore), mais vous etes
"sympas" compétents et francophones.

Je me sers d'une condition pour faire une "sorte" de sémaphore.

class RXEngine {
.../...
POSIXThread condition;
.../...

};

appelé par le thread "ordonnanceur" pour (re)donner la priorité au thread
"traitement"

//sans commentaire
void RXEngine::is_priority() {

mutex_priority.lock();
bool _priority = priority;
mutex_priority.unlock();

return _priority;
}

appelé par le thread "ordonnanceur" pour donner la priorité au thread
"traitement"

void RXEngine::set_priority() {

mutex_priority.lock();
if(!priority) {
priority = true;
condition.signal();
}
mutex_priority.unlock();
}

appelé par le thread "ordonnanceur" pour retirer la priorité au thread
"traitement"

void RXEngine::wait() {

mutex.lock();

mutex_priority.lock();
priority = false;
mutex_priority.unlock();

mutex.unlock();

}

Pendant le traitement ce code est évalué en "boucle"


.../...
if(!isPriority()) {
timer.wait();
condition.wait();
timer.restart();
}
.../...

Ce code me donne satisfaction (enfin presque sinon il n'y aurai pas de
question)

A la première utilisation mon thread traitement ne s'arrête pas, il faut
attendre le deuxième passage. Apres ça baigne.

Que se passe t'il?

Comment régler cela?

6 réponses

1 2
Avatar
pasde.hcyrano.spam
kanze wrote:

Non ! C'est le même mutex qui doit servir pour les deux.


je pense que c'est le paquetage qui est bizare.

http://www.partow.net/programming/posixsynchwrapper/index.html

#ifndef INCLUDE_POSIXCONDITION_H
#define INCLUDE_POSIXCONDITION_H

#include <pthread.h>
#include <sys/time.h>
#include "Utils.h"
#include "POSIXMutex.h"
#include "POSIXSynchronousException.h"
#include "POSIXSynchronousEvent.h"

class POSIXCondition
{

public:

POSIXCondition();
POSIXCondition(const POSIXCondition& obj);
~POSIXCondition();

void wait();
bool wait(unsigned int ms);
void signal();
void broadcast();

void lock_mutex();
void unlock_mutex();

private:

pthread_cond_t condition;
POSIXMutex mutex;
bool sent_signal;

unsigned int active_waiters;

};

#endif

void POSIXCondition::wait()
{
mutex.lock(); <=====

active_waiters++;

while (!sent_signal)
{
pthread_cond_wait(&condition, &(mutex.getMutex()));
}

active_waiters--;

if (active_waiters == 0)
sent_signal = false;

mutex.unlock(); <=====
};

j'ai suprimé ces 2 lignes partout dans le code de POSIXCondition:
et appliquer tes conseils et c'est ok.

merci
--
Bruno Causse
http://perso.wanadoo.fr/othello

Avatar
kanze
Bruno CAUSSE wrote:
dans l'article
, kanze à
a écrit le 21/03/06 17:43 :

}

void RXEngine::wait() {

mutex.lock();

mutex_priority.lock();
priority = false;
mutex_priority.unlock();

mutex.unlock();

}


Et ici, je ne vois pas du tout où tu attends la condition.


Je demande au moteur de s'arreter au prochain passage sur la
condition.


Je l'aurais appelé requestWait(), ou quelque chose du genre,
alors. Le mot wait(), tout seul, s'applique prèsque toujours à
la fonction où on attend.

Non. Tu t'obstines à vouloir séparer deux opérations qui ne
peuvent pas être séparées. La gestion de la condition et les
accès à la variable associée sont indissociable. La
variable, la condition et le mutex forment un tout. En
pseudo-code :

Note bien que la variable fait aussi partie du triplet. On
ne l'utilise que pour ça, et on ne s'amuse pas à la modifier
autrement.


je viens de le comprendre je crois.


C'est difficile d'expliquer en si peu de place. Si tu lis
l'anglais, je ne peux que te conseiller le Butenhof :
« Programming with POSIX Threads » (Addison-Wesley, ISBN
0-201-63392-2). (Je ne sais pas s'il a été traduit. Beaucoup de
chez Addison-Wesley l'est. Mais la qualité des traductions n'est
pas toujours très bien -- en général, il vaut mieux de
débrouiller avec l'original, si on peut. Et réalistiquement : si
on ne peut pas, il vaut mieux apprendre de le pouvoir. L'anglais
est incontournable si on veut rester à jour.)

En plus, je ne comprends pas la rôle du timer. Si le but est
de mettre un time-out, il existe un appel
pthread_cond_timedwait qui fait l'affaire.


Je programme un jeu de réflexion (Othello). Mon prog joue, sur
un serveur de jeu, des parties simultanées (2 parties
identiques en même temps avec les couleurs inversées) et
synchronisées (les coups sont joués en même temps)

Mon but : après avoir terminé la recherche d'un coup, si
l'adversaire n'a pas encore donné le sien (le prog anticipe le
coup suivant et réfléchi sur le temps adverse). Mon ordinateur
étant mono core je ne peux mené qu'une recherche a la fois. Le
prog possede deux moteurs, un par jeu. Les moteurs
fonctionnent donc alternativement. D'ou mes interruptions
(wait) de recherche (moteur A/B) pour lancer (un nouvelle
recherche si l'anticipation est mauvaise) ou poursuivre (si
l'anticipation est bonne) une recherche sur l'autre jeu
(moteur B/A).


Pour confirmer que j'ai bien compris, chaque thread
exécute une boucle un peu comme :

while ( ! gameOver ) {
semaphore.wait() ;
Microtime start = getTime() ;
while ( getTime() < start - turnLength ) {
// Un pas de calcul...
}
semaphore.unblock() ;
pause() ; // Pour être certain que l'autre thread
// ait le temps de prendre le semaphore.
}

Tout ça m'a l'air un peu compliqué. Et peut-être pas
nécessaire : sur la plupart des systèmes, les threads sont
time-slicés par défaut, ce qui veut dire que le système veut
automatiquement donner du temps d'abord à un, ensuite à l'autre.
Si ça ne suffisait pas dans la pratique, je crois que
j'utiliserais deux processus, plutôt que deux threads. Et pour
s'assurer que l'alternance se fait correctement, plutôt que de
compter sur le fait que l'autre prend la main avant que je ne
reviens le démander, j'utiliserait un sémaphore par thread -- en
fait, j'utiliserais deux MessageQueue, parce que c'est du code
que j'ai déjà sous la main, testé, avec un seul message comme
token (ou comme le baton d'un courreur dans un relai). La boucle
deviendrait donc quelque chose comme :

while ( ! gameOver ) {
std::auto_ptr< void > token = myMessageQueue.receive() ;
// boucle avec timer...
otherMessageQueue.send( token ) ;
}

Mais évidemment, ici, le message même est bidon, et on pourrait
facilement le remplacer par un simple booléan. Quelque chose du
genre :

while ( ! gameOver ) {
mySemaphore.wait() ;
mySemaphore.block() ;
// boucle avec timer ...
otherSemaphore.unblock() ;
}

L'ordonnanceur donne la priorité a un moteur en fonction de la
demande du serveur.

Le timer gère le temps (comme sur une partie d'échec)


Alors, je ne suis pas sûr d'avoir bien compris. C-à-d qu'on
débloque l'un ou l'autre thread, selon à qui le tour ? Et que
chaque thread a son propre timer, qui ne tourne que quand le
thread est actif ? Il faudrait que j'y reflechisse, mais je
crois qu'il faut un thread pour le timer (avec une priorité plus
élevée que les autres, si possible). Mais la gestion des temps
réels n'est jamais simple. On pourrait imaginer aussi que quand
le temps est épuisé, on cancelle le thread. Mais la cancellation
des threads est vraiment un topic avancé, surtout en C++, où
elle interagit avec les destructeeurs d'une façon qui dépend du
compilateur. (Je ne me rappelle plus qui fait quoi, mais je me
rappelle bien que sous Solaris, Sun CC et g++ se comportent
différemment par rapport à la cancellation.)

Aussi : si les classes de wrapper dont tu te sers n'offre
pas la possibilité de faire un scoped_lock, ou quelque chose
du genre, jette-les. Sinon, le moindre truc qui foire, et tu
risques de bloquer toute l'application.


Je travaille avec les thread posix depuis une ou deux semaine
seulement.


Alors, chaque chose dans son temps. Laisse tomber le timer
jusqu'à ce que la reste marche.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



Avatar
kanze
Bruno Causse wrote:
kanze wrote:

Non ! C'est le même mutex qui doit servir pour les deux.


je pense que c'est le paquetage qui est bizare.


Je pense que les auteurs n'ont rien compris:-).

http://www.partow.net/programming/posixsynchwrapper/index.html

#ifndef INCLUDE_POSIXCONDITION_H
#define INCLUDE_POSIXCONDITION_H

#include <pthread.h>
#include <sys/time.h>
#include "Utils.h"
#include "POSIXMutex.h"
#include "POSIXSynchronousException.h"
#include "POSIXSynchronousEvent.h"

class POSIXCondition
{
public:

POSIXCondition();
POSIXCondition(const POSIXCondition& obj);
~POSIXCondition();

void wait();
bool wait(unsigned int ms);
void signal();
void broadcast();

void lock_mutex();
void unlock_mutex();

private:
pthread_cond_t condition;
POSIXMutex mutex;
bool sent_signal;

unsigned int active_waiters;
};
#endif


Je veux bien qu'ils associent le condition et le mutex. C'est
même une bonne idée, puisqu'il faut les utiliser ensemble.

J'ai l'impression, en revanche, qu'ils ont emballé la variable
aussi (et je ne vois pas l'intérêt de « active_waiters »).
Supérficiellement, ça pourrait apparaître une bonne idée aussi,
puisqu'il faut l'utiliser avec les autres. Seulement, la raison
Posix ne l'a pas fait, c'est que le type de la variable dépend
de ce qu'on veut faire -- si tu régarde ma MessageQueue, par
exemple, la variable est un std::deque<void*> (et la condition
qu'on garde, c'est ! queue.empty(). Si on emballe aussi la
variable, alors 1) on ne doit pas donner accès au mutex, parce
qu'il ne sert à rien à l'utilisateur, et 2) il ne faut pas
l'appeler « condition », parce qu'il utilise une condition
précise pour implémenter quelque chose de plus haut niveau, et
ne permet pas de condition arbitraire.

void POSIXCondition::wait()
{
mutex.lock(); <=====

active_waiters++;

while (!sent_signal)
{
pthread_cond_wait(&condition, &(mutex.getMutex()));
}

active_waiters--;

if (active_waiters == 0)
sent_signal = false;

mutex.unlock(); <=====
};


C'est en fait une attente sur la condition interne, sent_signal.
Sauf que je ne comprends pas trop l'histoire de active_waiters ;
le signal va rester passant jusqu'à ce que tous les threads en
attente ont été éveillés. Pour que ça puisse même avoir l'air de
marcher, il faut ou bien qu'il n'y a jamais plus d'un thread qui
se met en attente, ou bien qu'on n'utilise que broadcast, et non
signal. Et même alors, ce n'est qu'un air quand il y a plus d'un
thread en attente, parce que même avec broadcast, rien ne
garantit que le premier thread éveillé ne revient pas dans wait
avant que les autres threads ont pris la main.

j'ai suprimé ces 2 lignes partout dans le code de
POSIXCondition: et appliquer tes conseils et c'est ok.


Je crois que le mieux serait de supprimer l'utilisation de cette
bibliothèque. Je te conseillerais vivement boost::thread -- ou
même, puisque le but est au moins partiellement pédogogique,
d'implémenter des petits wrappers toi-même : il te faut un
mutex, un scoped_lock, et alors, je te conseillerais des
utilisations déjà d'un niveau plus élevé des conditions, du
genre MessageQueue ou un sémaphore. Des utilisations qui ne
revèlent ni le mutex ni la condition Posix à l'utilisateur.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
kanze
twxs wrote:
kanze wrote:
...

Plutôt que Boost ou Ace qui m'effraies encore.


Ace m'effraie aussi. D'autant plus qu'il n'a pas marché pour
ce qu'il m'a fallu (un socket UDP), et que j'ai trouvé des
erreurs dans la gestion de thread par ailleurs.


Dans notre projet, nous avons migré notre bus CORBA vers
ACE/TAO et pour uniformiser le code la gestion des threads de
tout nos modules est également passée à ACE.

Pour l'instant nous n'avons pas rencontré de problème
"apparent" (attendons la prochaine demo devant le client ;) )
mais j'aimerai connaitre quelles sont les erreurs de gestions
auquel vous faites référence.


Il y a des accès sans protection à des variables qui peuvent
être modifiées par un autre thread (l'idiome « double checked
locking »). Tant que tu te trouves sur une machine mono-core, ou
sur une machine qui ne fait pas de lectures spéculatives, ça
devait marcher. Aussi, il n'y a modification que lors du premier
accès -- s'il n'y a pas de conflit lors du premier accès, tout
va bien. (Dans la plupart des utilisations, aussi, je crois que
l'erreur ne se manifestera que par une fuite de mémoire, quand
elle se manifeste.)

Ce qui me gène dans tout ça, c'est que l'erreur est connue des
auteurs, et qu'ils choisissent de ne pas le corréger, parce
qu'ils ne savent pas le mettre en évidence sur les machines à
leur disposition. C'est une attitude de base qui me fait
franchement peur -- c'est souvent très difficile à mettre en
évidence des erreurs de thread, mais ce n'est pas pour autant
qu'il faut les ignorer. Alors, du coup, je me démande s'il n'y
en a pas d'autres, qui risque encore plus de se manifester dans
la pratique, dans la vaste majorité du code que je n'ai pas
régardé.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



Avatar
Bruno CAUSSE
dans l'article , kanze à
a écrit le 22/03/06 9:46 :

Je l'aurais appelé requestWait(),


C'est fait,

j'utiliserais deux processus, plutôt que deux threads.


Les deux threads partagent un objet important une table de transposition
(implémentée sous la forme d'une table de hashache, tant que les deux jeux
sont identiques c'est un avantage, le moteur A bénéficie des recherches du
moteur B et inversement) après cette grande table (500 Mo) est
"virtuellement" divisée en deux, ce qui evite de la synchronisée (qui est
aussi un avantage : les deux moteur ne se "polluent" pas)


Et pour
s'assurer que l'alternance se fait correctement, plutôt que de
compter sur le fait que l'autre prend la main avant que je ne
reviens le demander, j'utiliserait un sémaphore par thread.


C'est ce qui se passe chaque moteur a un sémaphore.

Alors, je ne suis pas sûr d'avoir bien compris. C-à-d qu'on
débloque l'un ou l'autre thread, selon à qui le tour ?


Oui, en fait

L'ordonnaceur demande au thread actif (obligatoirement en recherche sur le
temps adverse) de se mettre en attente.
Lance une requete sur l'autre moteur qui verifie la validité de
l'anticipation sur ce moteur.

2 cas possibles : bonne == reveil, la fin de la recherche interviendra quand
le temps imparti est terminé ou insuffusant pour poursuivre la recherche, ou
bien sur sur game over

: mauvaise == lève le flag interrupt, réveil, attend (join)
que le thread se termine "proprement" (remise en ordre a faire, le type de
calcul est récursive). Et lance un nouveau thread (nouvelle recherche)

Et que
chaque thread a son propre timer, qui ne tourne que quand le
thread est actif ?


Le timer est "sûrement impropre" c'est simplement un wrapper sur
gettimeofday() avec diverses méthodes type chronomètre

J'avais penser un temps a regler l'ordonnacement des threads cela avec les
priorités (faible priorité sur le thread qui doit attendre, haute sur
l'autre) mais je n'ai pas essayé (penses tu que l'idee est bonne?)

Avatar
Bruno CAUSSE
dans l'article C0483BFF.17A0B%, Bruno CAUSSE à
a écrit le 23/03/06 11:50 :

Les deux threads partagent un objet important une table de transposition
(implémentée sous la forme d'une table de hashache, tant que les deux jeux
sont identiques c'est un avantage, le moteur A bénéficie des recherches du
moteur B et inversement) après cette grande table (500 Mo) est
"virtuellement" divisée en deux, ce qui evite de la synchronisée


Car a terme j'aimerai que mon prog tourne sur un dual-core, les deux moteurs
ne seraient jamais en "attente" :-)

(qui est
aussi un avantage : les deux moteur ne se "polluent" pas)


1 2