2010-10-04 5 views
0

J'ai une question concernant un mot-clé volatile pour lequel je n'arrive pas à trouver de réponse.Classes volatiles en C++

Dans mon application, j'ai une classe de données qui est partagée en tant que tampon d'état entre les threads, et j'ai besoin qu'il soit mis à jour régulièrement à partir de plusieurs threads.

classe ressemble à ceci:

class CBuffer 
{ 
    //Constructor, destructor, Critical section initialization/destruction 
    //... 

    volatile wstring m_strParam; 
    //... 
    void InterlockedParamSetter(const wstring &strIn); 
    wstring InterlockedParamGetter(); 

    ParamSetter(const wstring &strIn); 
    wstring ParamGetter(); 

    Lock(); 
    Unlock(); 
} 

void CBuffer::InterlockedParamSetter(const wstring &strIn) 
{ 
    Lock(); 
    const_cast<wstring>(m_strParam) = strIn; 
    Unlock(); 
} 

//... other f-ns 

Mais le compilateur se plaint de la conversion const_cast. Il semble presque que j'utilise abusivement le mot clé volatile, mais en même temps, je ne peux pas laisser les params être mis en cache entre les appels, parce que si deux ou trois threads vont les assigner, quelque chose peut mal se passer.

Comment écrire des classes sécurisées thread/cache en C++?

P.S .: Jusqu'à présent, le verrouillage n'est pas le goulot d'étranglement, et les verrous sont assez simples, donc pour l'instant, la sérialisation et le verrouillage ne sont pas un problème du point de vue des performances. Bien sûr, s'il y a un meilleur algorithme, j'écouterai volontiers.

EDIT: Je suis toujours pas clair ...

Considérons cet exemple (inline + temps lien codegen);

void Thread1Func() 
{ 
    //Unrolled, inlined InterlockedParamSetter() 
    EnterCriticalSection(&cs); 
    WriteTo(CBuffer::m_strParam);//write to buffer, wstring not volatile, cache it 
    LeavCriticalSection(&cs); 
    //Unroll end 

    //DoSomethingElse 

    //!!!!Thread 2 does InterlockedParamSetter 
    //which causes wstring::reserve and invalidates old pointers!!!! 

    //Unrolled, inlined InterlockedParamSetter() 
    EnterCriticalSection(&cs); 
    WriteTo(CBuffer::m_strParam);//oh, good, we have a pointer to old buffer 
    //cached in one of the registers, write to it -> access violation 
    LeavCriticalSection(&cs); 
    //Unroll end 
} 
+1

Avez-vous lu l'un des autres messages marqués "volatile"? Ce que ce mot-clé fait/n'a pas été discuté longuement ici. –

+0

J'ai essayé de chercher, mais je n'ai pas trouvé de réponse définitive à propos des objets et de leurs optimisations en l'absence de génération de code temporel volatile et inline + link. – Coder

+0

Réponse: http://stackoverflow.com/questions/3612505/is-volatile-needed-in-this-multi-threaded-c-code/3612551#3612551 – rwong

Répondre

2

Dans le code portable, volatile a absolument rien à voir avec multithreading.

En MSVC, comme une extension, volatile types natifs simples aux qualifiés tels que int peut être utilisé avec des opérations simples de lecture et de stockage pour les accès atomiques, mais ce n'a pas étendre à lecture-modification-écriture accède à un tel comme i++ ou à des objets de type classe tels que std::string. Pour garantir un accès sécurisé à votre variable m_strParam à partir de plusieurs threads, chaque thread doit verrouiller le mutex associé avant d'accéder au m_strParam et le déverrouiller ensuite. Le verrouillage/déverrouillage assurera que la valeur correcte de m_strParam est vue par chaque thread --- il n'y aura pas de mise en cache du pointeur interne si un autre thread a modifié la variable par le verrou suivant.

Si vous utilisez correctement les verrous, vous n'avez pas besoin du qualificatif volatile et vous n'avez pas besoin d'utiliser const_cast.

+0

"il n'y aura pas de mise en cache du pointeur interne si un autre thread a modifié la variable par le verrou suivant." Y a-t-il de la documentation à ce sujet, je n'en trouve pas. Voir édition dans l'OP. – Coder

+0

Déverrouiller et ensuite verrouiller le ** mutex ** même garantira que toutes les modifications apportées par le thread de déverrouillage avant l'appel 'unlock()' sont visibles pour le thread de verrouillage après l'appel 'lock()'. Si le 'cs' dans yout' Thread1Func' est le même 'CRITICAL_SECTION' référencé par' InterlockedParamSetter' alors ce sera très bien. S'il s'agit d'une section critique différente, cela ne vous rapportera rien --- les verrous sur des mutex indépendants n'imposent pas de contraintes d'ordre ou de visibilité les uns aux autres. –

+0

Ensuite, je n'ai pas besoin de la barrière de mémoire ou d'un autre marqueur spécifique? Un wstring uni gardé par le même CS dans tous les threads est-il suffisant? – Coder

2

std::string (et std::wstring) n'a pas été conçu pour être volatil et je vous conseille de ne pas utiliser de cette façon.

La méthode habituelle pour garantir la sécurité des threads consiste à utiliser des mutex, des sémaphores, le cas échéant, et à éviter l'utilisation de variables globales.

Et comme Marcus Lindblom mentionner, il y a généralement une clôture lecture/écriture dans les mécanismes de verrouillage qui prennent soin de traiter les problèmes de mise en cache potentiels. Donc, vous devriez être en sécurité.

+0

Le verrouillage et déverrouillage est effectué via la section critique, mais fait que garantir que le contenu du wstring ne sera pas mis en cache s'il n'est pas volatile. Say thread 1 obtient la valeur, met en cache le pointeur interne (? À cause du code de temps de liaison gen + inlining?), 2ème thread écrit à wstring, premier thread écrase la valeur via l'accès direct au pointeur en cache. – Coder

+3

@MadMan: Il y a généralement des barrières de lecture/écriture dans les verrous qui s'occupent de cela. (Par exemple, synchro d'état à travers les threads sur le verrouillage/déverrouillage). – Macke

+0

@Marcus Lindblom: Merci, je vais mettre à jour ma réponse pour le rendre plus précis. – ereOn

2

Vous devez const_cast strIn pas m_strParam:

m_strParam = const_cast<wstring> (strIn); 

Il n'y a pas d'idiomes généralement acceptés sur fil/cache des classes de sécurité en C++, comme la norme C++ qui prévaut actuellement et ne dit rien sur la programmation concurrente. volatile ne fait aucune garantie d'atomicité et est inutile dans l'écriture de programmes portables, thread-safe. Voir ces liens:

+0

Je ne pense pas que le compilateur soit libre d'ignorer 'volatile'. Il peut ignorer 'register' ou' inline', mais pas 'volatile '. – DevSolar

+0

Tout à fait. Si vous voulez un langage de programmation système conçu autour de la programmation concurrente, regardez Ada. Soit cela, soit vous attendez une future norme C++ où ils parviendront à le verrouiller. –

+0

@DevSolar Un compilateur ne peut pas ignorer 'volatile' mais il n'est d'aucune aide dans l'écriture de programmes multithread. J'ai mis à jour ma réponse. –

0

Pas besoin d'utiliser volatile. Un compiler memory barrier devrait être suffisant. Le Lock/Unlock est toujours nécessaire, cependant. C'est,

class CBuffer 
{ 
    //Constructor, destructor, Critical section initialization/destruction ... 
    wstring m_strParam; 
}; 

void CBuffer::InterlockedParamSetter(const wstring &strIn)  
{ 
    Lock(); 
    //compiler-specific memory barrier; 
    m_strParam = strIn; 
    //compiler-specific memory barrier; 
    Unlock(); 
} 

Bien que sur certains compilateurs, le mot-clé volatile a la même signification que la barrière de mémoire lorsqu'elle est appliquée à des types non primitifs comme wstring.Exemple: VC++

mémoire de marquage avec une barrière de mémoire est similaire à la mémoire de marquage avec le mot-clé volatile (C++). Cependant, une barrière de mémoire est plus efficace parce que ...