Clôtures en C++0x, garanties uniquement sur les atomes ou la mémoire en général

Clôtures en C++0x, garanties uniquement sur les atomes ou la mémoire en général

Les clôtures fournissent l'ordre sur toutes les données . Cependant, afin de garantir que l'opération de clôture d'un thread est visible à une seconde, vous devez utiliser des opérations atomiques pour le drapeau, sinon vous avez une course de données.

std::atomic<bool> ready(false);
int data=0;

void thread_1()
{
    data=42;
    std::atomic_thread_fence(std::memory_order_release);
    ready.store(true,std::memory_order_relaxed);
}

void thread_2()
{
    if(ready.load(std::memory_order_relaxed))
    {
        std::atomic_thread_fence(std::memory_order_acquire);
        std::cout<<"data="<<data<<std::endl;
    }
}

Si thread_2 lit ready avoir true , alors les clôtures garantissent que data peut être lu en toute sécurité, et la sortie sera data=42 . Si ready est lu comme étant false , alors vous ne pouvez pas garantir que thread_1 a émis la clôture appropriée, donc une clôture dans le fil 2 ne fournirait toujours pas les garanties de commande nécessaires --- si le if en thread_2 a été omis, l'accès à data serait une course aux données et un comportement indéfini, même avec la clôture.

Précision :Un std::atomic_thread_fence(std::memory_order_release) est généralement équivalente à une clôture de magasin et sera probablement mise en œuvre en tant que telle. Cependant, une clôture unique sur un processeur ne garantit aucun ordre de mémoire :vous avez besoin d'une clôture correspondante sur un second processeur, ET vous devez savoir que lorsque la clôture d'acquisition a été exécutée, les effets de la clôture de libération étaient visibles pour ce deuxième processeur. Il est évident que si la CPU A émet une clôture d'acquisition, puis 5 secondes plus tard, la CPU B émet une clôture de libération, alors cette clôture de libération ne peut pas se synchroniser avec la clôture d'acquisition. À moins que vous n'ayez un moyen de vérifier si la clôture a été émise ou non sur l'autre CPU, le code sur la CPU A ne peut pas dire s'il a émis sa clôture avant ou après la clôture sur la CPU B.

L'exigence d'utiliser une opération atomique pour vérifier si la clôture a été vue ou non est une conséquence des règles de course aux données :vous ne pouvez pas accéder à une variable non atomique à partir de plusieurs threads sans relation d'ordre, vous ne pouvez donc pas utiliser une variable non-atomique. variable atomique pour vérifier une relation d'ordre.

Un mécanisme plus fort tel qu'un mutex peut bien sûr être utilisé, mais cela rendrait la clôture séparée inutile, car le mutex fournirait la clôture.

Les opérations atomiques assouplies sont probablement de simples chargements et stockages sur des processeurs modernes, mais éventuellement avec des exigences d'alignement supplémentaires pour garantir l'atomicité.

Le code écrit pour utiliser des clôtures spécifiques au processeur peut facilement être modifié pour utiliser des clôtures C++0x, à condition que les opérations utilisées pour vérifier la synchronisation (plutôt que celles utilisées pour accéder aux données synchronisées) soient atomiques. Le code existant peut très bien s'appuyer sur l'atomicité des chargements simples et des magasins sur un processeur donné, mais la conversion en C++0x nécessitera l'utilisation d'opérations atomiques pour ces vérifications afin de fournir les garanties de commande.


D'après ce que j'ai compris, ce sont de véritables clôtures. La preuve circonstancielle étant qu'après tout, ils sont destinés à correspondre aux fonctionnalités trouvées dans le matériel réel et qui permettent une mise en œuvre efficace des algorithmes de synchronisation. Comme vous le dites, les clôtures qui ne s'appliquent qu'à certaines valeurs spécifiques sont 1. inutiles et 2. introuvables sur le matériel actuel.

Cela étant dit, AFAICS la section que vous citez décrit la relation "synchronise avec" entre les clôtures et les opérations atomiques. Pour une définition de ce que cela signifie, voir la section 1.10 Exécutions multithreads et courses de données . Encore une fois, AFAICS, cela n'implique pas que les clôtures s'appliquent uniquement aux objets atomiques, mais je soupçonne plutôt que le sens est que, même si les charges et les magasins ordinaires peuvent passer, acquérir et libérer les clôtures de la manière habituelle (une seule direction), les charges atomiques/ les magasins ne le peuvent pas.

Wrt. objets atomiques, ma compréhension est que sur toutes les cibles prises en charge par Linux, des variables entières simples correctement alignées dont sizeof() <=sizeof(*void) sont atomiques, donc Linux utilise des entiers normaux comme variables de synchronisation (c'est-à-dire que les opérations atomiques du noyau Linux fonctionnent sur des variables entières normales). C++ ne veut pas imposer une telle limitation, d'où les types d'entiers atomiques séparés. De plus, en C++, les opérations sur les types entiers atomiques impliquent des barrières, alors que dans le noyau Linux, toutes les barrières sont explicites (ce qui est assez évident puisque sans le support du compilateur pour les types atomiques, c'est ce qu'il faut faire).