2010-07-29 5 views
2

J'ai des données qui sont à la fois lues et mises à jour par plusieurs threads. Les lectures et les écritures doivent être atomiques. Je pensais à le faire comme ceci:Lecteur/graveur sans verrou

// Values must be read and updated atomically 
struct SValues 
{ 
    double a; 
    double b; 
    double c; 
    double d; 
}; 

class Test 
{ 
public: 
    Test() 
    { 
     m_pValues = &m_values; 
    } 

    SValues* LockAndGet() 
    { 
     // Spin forver until we got ownership of the pointer 
     while (true) 
     { 
      SValues* pValues = (SValues*)::InterlockedExchange((long*)m_pValues, 0xffffffff); 
      if (pValues != (SValues*)0xffffffff) 
      { 
       return pValues; 
      } 
     } 
    } 

    void Unlock(SValues* pValues) 
    { 
     // Return the pointer so other threads can lock it 
     ::InterlockedExchange((long*)m_pValues, (long)pValues); 
    } 

private: 
    SValues* m_pValues; 
    SValues m_values; 
}; 

void TestFunc() 
{ 
    Test test; 

    SValues* pValues = test.LockAndGet(); 

    // Update or read values 

    test.Unlock(pValues); 
} 

Les données sont protégées en volant le pointeur pour chaque lecture et d'écriture, ce qui devrait threadsafe, mais il nécessite deux instructions enclenchées pour chaque accès. Il y aura beaucoup de lectures et d'écritures et je ne peux pas dire à l'avance s'il y aura plus de lectures ou plus d'écritures.

Cela peut-il être plus efficace que cela? Cela se verrouille également lors de la lecture, mais comme il est tout à fait possible d'avoir plus d'écritures puis de lectures, il n'y a pas lieu d'optimiser la lecture, à moins de ne pas infliger de pénalité à l'écriture. Je pensais à lire l'acquisition du pointeur sans une instruction verrouillée (avec un numéro de séquence), copier les données, puis avoir un moyen de dire si le numéro de séquence avait changé, auquel cas il devrait réessayer. Cela exigerait cependant des barrières de mémoire, et je ne sais pas si cela pourrait améliorer la vitesse.

----- ----- EDIT

Merci à tous, grands commentaires! Je n'ai pas réellement exécuté ce code, mais je vais essayer de comparer la méthode actuelle avec une section critique plus tard aujourd'hui (si j'ai le temps). Je suis toujours à la recherche d'une solution optimale, donc je reviendrai aux commentaires les plus avancés plus tard. Merci encore!

+0

Quel est le problème avec l'aide des primitives de synchronisation de threads par défaut? – naivnomore

+0

Je dois admettre que j'ai juste supposé que je pouvais le faire plus vite que ça. 1) Je ne montre qu'une seule instance ici, mais en réalité j'aurai peut-être 10000 instances de ces enregistrements de données protégées, ce qui signifierait 10000 sections critiques. Mais peut-être que ce n'est pas un problème, je ne sais pas, je n'ai jamais essayé quelque chose comme ça. 2) J'espérais pouvoir trouver quelque chose de plus rapide qu'une section critique. Il peut facilement y avoir des millions de lectures/écritures par seconde. Et sur le plan personnel, j'ai juste pensé que ce serait amusant de le faire aussi vite qu'humainement (machinely?) Possible. – Rabbit

+0

Windows CRITICAL_SECTION est très léger à moins qu'il ne doive réellement bloquer. Je ne pense pas que des threads d'utilisateurs occupés comme celui-ci soient une très bonne idée - vous signalez implicitement au planificateur que vous avez beaucoup à faire, alors qu'en fait c'est le contraire. –

Répondre

3

Ce que vous avez écrit est essentiellement un spinlock. Si vous allez faire cela, alors vous pourriez aussi bien utiliser un mutex, tel que boost::mutex. Si vous voulez vraiment un spinlock, utilisez un système fourni par le système, ou un d'une bibliothèque plutôt que d'écrire le vôtre.

D'autres possibilités incluent une forme de copie-sur-écriture. Stocker la structure de données par pointeur, et juste lire le pointeur (atomiquement) sur le côté de lecture. Du côté écriture, créez une nouvelle instance (en copiant les anciennes données si nécessaire) et échangez le pointeur de manière atomique. Si l'écriture a besoin de l'ancienne valeur et qu'il y a plus d'un éditeur, vous devrez soit faire une boucle d'échange de comparaison pour vous assurer que la valeur n'a pas changé depuis la lecture (attention aux problèmes ABA), soit un mutex pour les écrivains. Si vous faites cela alors vous devez faire attention à la façon dont vous gérez la mémoire --- vous avez besoin d'un moyen de récupérer des instances des données quand aucun thread ne le référence (mais pas avant).

+0

C'est vrai, c'est un spinlock. Après un peu de recherche, il semble qu'un spinlock peut être implémenté sans exiger une instruction verrouillée à la sortie et sans rien écrire lors de la rotation. C'est peut-être la solution que je cherche. Je suis occupé maintenant mais j'y reviendrai plus tard. – Rabbit

+0

Ma meilleure solution jusqu'à maintenant est d'utiliser un spinlock par pointeur que je dois protéger. Ils n'utilisent que 4 octets chacun. C'est essentiellement le même que le code d'exemple dans cette question, sauf que le spinlock n'utilisera pas une instruction interlocked pendant la rotation, et il utilise l'instruction asm pause. – Rabbit

2

Il existe plusieurs façons de résoudre ce problème, en particulier sans mutex ou mécanismes de verrouillage. Le problème est que je ne suis pas sûr quelles sont les contraintes sur votre système. Rappelez-vous que les opérations atomiques sont souvent déplacées par les compilateurs en C + +.

En général, je résoudre le problème comme celui-ci:

multiples producteurs unique aux consommateurs en 1 seul producteur-consommateur par un seul fil d'écriture. Chaque thread écrit dans sa propre file d'attente. Un thread de consommation unique qui rassemble les données produites et les stocke dans un stockage de données à consommateur unique et à plusieurs lecteurs. La mise en œuvre pour cela est beaucoup de travail et seulement recommandé si vous faites une application à temps critique et que vous avez le temps de mettre cette solution.

Il y a plus de choses à lire sur ce, depuis la mise en œuvre est la plate-forme spécifique:

atomique etc opérations sur les fenêtres/Xbox360: http://msdn.microsoft.com/en-us/library/ee418650(VS.85).aspx

Le multithread seul producteur unique consommateur sans serrures :
http://www.codeproject.com/KB/threads/LockFree.aspx#heading0005

Qu'est-ce que pour "volatile" est vraiment et peut être utilisé:
http://www.drdobbs.com/cpp/212701484

Herb Sutter a écrit un bon article qui vous rappelle les dangers de la rédaction de ce type de code: http://www.drdobbs.com/cpp/210600279;jsessionid=ZSUN3G3VXJM0BQE1GHRSKHWATMY32JVN?pgno=2

+0

Une autre bonne solution pour une file d'attente sans verrou: http://www.drdobbs.com/architecture-and-design/210604448 – Simon

+0

Et s'il vous plaît s'il vous plaît soyez conscient des conditions de course. – Simon

Questions connexes