web-dev-qa-db-fra.com

Comment utiliser std :: atomic <> efficacement pour les types non primitifs?

Les définitions de std::atomic<> semble montrer son utilité évidente pour les types primitifs ou peut-être POD.

Quand l'utiliseriez-vous réellement pour les cours?

Quand devriez-vous éviter de l'utiliser pour les cours?

45
kfmfe04

Les opérations que std::atomic Rend disponibles sur n'importe quel type trivialement copiable sont assez basiques. Vous pouvez construire et détruire atomic<T>, Vous pouvez demander si le type is_lock_free(), vous pouvez charger et stocker des copies de T, et vous pouvez échanger des valeurs de T de diverses façons. Si cela suffit pour votre objectif, vous feriez mieux de le faire que de détenir un verrou explicite.

Si ces opérations ne sont pas suffisantes, si par exemple vous devez effectuer atomiquement des opérations de séquence directement sur la valeur, ou si l'objet est suffisamment grand pour que la copie coûte cher, alors vous voudrez probablement tenir un verrou explicite que vous gérez pour atteindre vos objectifs plus complexes ou éviter de faire toutes les copies qu'impliquerait atomic<T>.

// non-POD type that maintains an invariant a==b without any care for
// thread safety.
struct T { int b; }
struct S : private T {
    S(int n) : a{n}, b{n} {}
    void increment() { a++; b++; }
private:
    int a;
};

std::atomic<S> a{{5}}; // global variable

// how a thread might update the global variable without losing any
// other thread's updates.
S s = a.load();
S new_s;
do {
    new_s = s;
    new_s.increment(); // whatever modifications you want
} while (!a.compare_exchange_strong(s, new_s));

Comme vous pouvez le voir, cela obtient essentiellement une copie de la valeur, modifie la copie, puis essaie de recopier la valeur modifiée, en répétant si nécessaire. Les modifications que vous apportez à la copie peuvent être aussi complexes que vous le souhaitez, pas simplement limitées aux fonctions à un seul membre.

30
bames53

Il fonctionne pour les types primitifs et POD. Le type doit être memcpyable, donc des classes plus générales sont sorties.

14
Pete Becker

La norme dit que

Les spécialisations et les instanciations du modèle atomique doivent avoir un constructeur de copie supprimé, un opérateur d'affectation de copie supprimé et un constructeur de valeur constexpr.

Si c'est exactement la même chose que la réponse de Pete Becker, je n'en suis pas sûr. J'interprète cela de telle sorte que vous êtes libre de vous spécialiser dans votre propre classe (pas seulement dans les classes mémorables).

7
Johan Lundberg

Je préfère std :: mutex pour ce genre de scénarios. Néanmoins, j'ai essayé un banc d'essai médiocre pour profiler une version avec std :: atomics et std :: mutex dans un environnement à thread unique (et donc parfaitement synchronisé).

#include <chrono>
#include <atomic>
#include <mutex>

std::mutex _mux;
int i = 0;
int j = 0;
void a() {
    std::lock_guard<std::mutex> lock(_mux);
    i++;
    j++;
}

struct S {
    int k = 0;
    int l = 0;

    void doSomething() {
        k++;
        l++;
    }
};

std::atomic<S> s;
void b() {
    S tmp = s.load();
    S new_s;
    do {
        new_s = tmp;
        //new_s.doSomething(); // whatever modifications you want
        new_s.k++;
        new_s.l++;
    } while (!s.compare_exchange_strong(tmp, new_s));
}

void main(void) {

    std::chrono::high_resolution_clock clock;

    auto t1 = clock.now();
    for (int cnt = 0; cnt < 1000000; cnt++)
        a();
    auto diff1 = clock.now() - t1;

    auto t2 = clock.now();
    for (int cnt = 0; cnt < 1000000; cnt++)
        b();
    auto diff2 = clock.now() - t2;

    auto total = diff1.count() + diff2.count();
    auto frac1 = (double)diff1.count() / total;
    auto frac2 = (double)diff2.count() / total;
}

sur mon système, la version utilisant std :: mutex était plus rapide que l'approche std :: atomic. Je pense que cela est dû à la copie supplémentaire des valeurs. De plus, s'il est utilisé dans un environnement multithread, le bouclage occupé peut également affecter les performances.

En résumé, oui, il est possible d'utiliser std :: atomic avec différents types de pods, mais dans la plupart des cas, std :: mutex est l'arme de choix, car il est intentionnellement plus facile de comprendre ce qui se passe, et n'est donc pas aussi enclin aux bogues comme la version présentée avec le std :: atomic.

1
pocketbroadcast