Est-ce que const signifie thread-safe en C++11?

J'entends que const signifie thread-safe dans C++11. Est-ce vrai?

Cela signifie - const est maintenant l'équivalent de Java's synchronized?

Sont-ils à court de mots-clés?

111
demandé sur Johannes Schaub - litb 2013-01-02 22:43:20

1 réponses

J'entends que const signifie thread-safe dans C++11. Est-ce vrai?

C'est un peu vrai...

C'est ce que le langage Standard A à dire sur la sécurité des threads:

[1.10/4] Deux évaluations d'expressions entrent en conflit avec si l'une d'elles modifie un emplacement mémoire (1.7) et que l'autre accède ou modifie le même emplacement mémoire.

[1.10/21] L'exécution d'un programme, contient un données course si elle contient deux actions contradictoires dans des threads différents, dont au moins une n'est pas atomique, et ni arrive avant l'autre. Une telle course de données entraîne un comportement indéfini.

Qui n'est rien d'autre que la condition suffisante pour qu'une course de données se produise:

  1. Il y a deux ou plusieurs actions effectuées en même temps sur une chose donnée; et
  2. au moins l'un d'eux est une écriture.

La bibliothèque Standard s'appuie sur cela, en allant un peu plus loin:

[17.6.5.9/1] Cette section spécifie les exigences que les implémentations doivent respecter pour empêcher les courses de données (1.10). Chaque fonction de bibliothèque standard doit répondre à chaque exigence, sauf indication contraire. Les implémentations peuvent empêcher les courses de données dans des cas autres que ceux spécifiés ci-dessous.

[17.6.5.9/3] Une fonction de bibliothèque standard C++ ne doit pas modifier directement ou indirectement les objets (1.10) accessibles par des threads autres que le thread courant, sauf si les objets sont accessibles directement ou indirectement via les arguments non-const de la fonction, y compris this.

Qui, en termes simples, dit qu'il s'attend à ce que les opérations sur les objets const soient thread-safe. Cela signifie que la bibliothèque Standard n'introduira pas course de données tant que les opérations sur const objets de vos propres types soit

  1. se composent entièrement de lectures-c'est-à-dire qu'il n'y a pas d'Écritures -; ou
  2. synchronise en interne les Écritures.

Si cette attente ne tient pas pour l'un de vos types, l'utiliser directement ou indirectement avec n'importe quel composant de la bibliothèque Standard peut entraîner une course de données . En conclusion, const signifie thread-safe de la Standard Bibliothèque point de vue. Il est important de noter que ce n'est qu'un contrat et il ne sera pas appliquée par le compilateur, si vous le cassez vous obtenez comportement indéfini et vous êtes sur votre propre. Si const est présent ou non n'affectera pas la génération de code-du moins pas en ce qui concerne courses de données --.

Cela signifie - const est maintenant l'équivalent de Java's synchronized?

Pas de. Pas à tout...

Considérons la classe trop simplifiée suivante représentant un rectangle:

class rect {
    int width = 0, height = 0;

public:
    /*...*/
    void set_size( int new_width, int new_height ) {
        width = new_width;
        height = new_height;
    }
    int area() const {
        return width * height;
    }
};

La fonction membre area est thread-safe ; pas parce que son const, mais parce qu'il est entièrement constitué d'opérations de lecture. Il n'y a pas d'écriture impliquée, et au moins une écriture impliquée est nécessaire pour qu'une course de données se produise. Cela signifie que vous pouvez appeler area à partir d'autant de threads que vous le souhaitez et vous obtiendrez des résultats corrects temps.

Notez que cela ne signifie pas que rect est thread-safe. En fait, il est facile de voir comment si un appel à area devait se produire en même temps qu'un appel à set_size sur un rect donné, alors area pourrait finir par calculer son résultat en fonction d'une ancienne largeur et d'une nouvelle hauteur (ou même sur des valeurs brouillées).

Mais c'est bien, rect n'est pas const donc il ne devrait même pas être thread-safe après tout. Un objet déclaré const rect, d'autre part, serait thread-safe, puisque aucun des écritures sont possibles (et si vous envisagez de const_cast-chose à l'origine déclarée const vous obtenez undefined-comportement et c'est tout).

, de Sorte que veut dire alors?

Supposons --pour des raisons d'argumentation-- que les opérations de multiplication sont extrêmement coûteuses et qu'il vaut mieux les éviter lorsque cela est possible. Nous pourrions calculer la zone uniquement si elle est demandée, puis la mettre en cache au cas où elle serait à nouveau demandée dans le avenir:

class rect {
    int width = 0, height = 0;

    mutable int cached_area = 0;
    mutable bool cached_area_valid = true;

public:
    /*...*/
    void set_size( int new_width, int new_height ) {
        cached_area_valid = ( width == new_width && height == new_height );
        width = new_width;
        height = new_height;
    }
    int area() const {
        if( !cached_area_valid ) {
            cached_area = width;
            cached_area *= height;
            cached_area_valid = true;
        }
        return cached_area;
    }
};

[Si cet exemple semble trop artificiel, vous pouvez remplacer mentalement int par un très grand entier alloué dynamiquement qui est intrinsèquement non thread-safe et pour lequel les multiplications sont extrêmement coûteuses.]

Le membre de la fonction area n'est plus thread-safe, elle écrit maintenant et n'est pas synchronisé en interne. Est-ce un problème? L'appel de area peut se produire dans le cadre d'un constructeur par copie d'un autre objet, un tel constructeur aurait pu être appelé par une opération sur un conteneur standard , et à ce stade la bibliothèque standard s'attend à ce que cette opération se comporte comme une lecture en ce qui concerne courses de données. Mais nous faisons des Écritures!

Dès Que nous avons mis un rect dans a conteneur standard, directement ou indirectement-- nous entrons dans une contrat avec Bibliothèque Standard. Pour continuer à faire écrit dans une fonction const tout en honorant ce contrat, nous devons synchroniser en interne ces Écritures:

class rect {
    int width = 0, height = 0;

    mutable std::mutex cache_mutex;
    mutable int cached_area = 0;
    mutable bool cached_area_valid = true;

public:
    /*...*/
    void set_size( int new_width, int new_height ) {
        if( new_width != width || new_height != height )
        {
            std::lock_guard< std::mutex > guard( cache_mutex );

            cached_area_valid = false;
        }
        width = new_width;
        height = new_height;
    }
    int area() const {
        std::lock_guard< std::mutex > guard( cache_mutex );

        if( !cached_area_valid ) {
            cached_area = width;
            cached_area *= height;
            cached_area_valid = true;
        }
        return cached_area;
    }
};

Notez que nous avons fait la area function thread-safe, mais le rect n'est pas encore thread-safe. Un appel à area se produisant en même temps qu'un appel à set_size peut encore finir par calculer la mauvaise valeur, puisque les affectations à width et height ne sont pas protégées par le mutex.

Si nous le voulions vraiment un thread-safe rect, nous utiliser les primitives de synchronisation pour protéger les non thread-safe rect.

Sont-ils à court de mots-clés?

Oui, ils le sont. Ils sont à court de mots-clés depuis le premier jour.


Source: Vous ne savez pas const et mutable - Herb Sutter

123
répondu K-ballo 2013-05-30 20:53:22