lundi 6 décembre 2010

C++0x - std::atomic

Les variables atomiques dans c++0x fournissent (enfin!) une manière standard pour protéger les types de base, avec un support automatique des instructions atomiques quand elles sont disponibles. Voyons le programme suivant avec g++4.4.


#include <thread>
#include <iostream>

void increment(int & counter)
{
for(size_t i = 0; i < 10000; i++)
{
counter++;
}
}

void decrement(int & counter)
{
for(size_t i = 0; i < 10000; i++)
{
counter--;
}
}

int main()
{
int counter;

std::thread t1(&increment, std::ref(counter));
std::thread t2(&decrement, std::ref(counter));

t1.join();
t2.join();

std::cout << counter << std::endl;

return 0;
}

C'est prévisible, le programme va afficher des nombres totalement farfelus en sortie, car counter n'est pas protégé.

Maintenant, protégeons counter:

#include <cstdatomic>
#include <thread>
#include <iostream>

void increment(std::atomic<int> & counter)
{
for(size_t i = 0; i < 10000; i++)
{
counter++;
}
}

void decrement(std::atomic<int> & counter)
{
for(size_t i = 0; i < 10000; i++)
{
counter--;
}
}

int main()
{
std::atomic<int> counter;

std::thread t1(&increment, std::ref(counter));
std::thread t2(&decrement, std::ref(counter));

t1.join();
t2.join();

std::cout << counter << std::endl;

return 0;
}

Magie de la technologie moderne, le programme affiche systématiquement un beau 0 tout rond. Un appel de la méthode is_lock_free() sur counter (atomic_is_lock_free() sur gcc 4.5, plus avancé au niveau des standards) vous dira si votre architecture supporte les instructions atomiques.

Voilà, c'est à peu près tout! A noter cependant que l'en-tête atomic a fait son arrivée dans gcc 4.5. Avec cette version, le programme d'exemple (de mémoire), serait (et avec les lambdas, s'il vous plait!):

#include <atomic>
#include <thread>
#include <iostream>

int main()
{
std::atomic<int> counter;

std::thread t1([&counter]()
{for(size_t i = 0; i < 10000; i++) counter++;});
std::thread t2([&counter]()
{for(size_t i = 0; i < 10000; i++) counter--;});

t1.join();
t2.join();

std::cout << counter << std::endl;

return 0;
}

6 commentaires:

Anonyme a dit…

Très belle présentation du mot clé. Très clair. Merci beaucoup.

Ingo Müller a dit…
Ce commentaire a été supprimé par l'auteur.
Ingo Müller a dit…

Salut !

J'ai du chercher un peu avant de savoir comment on compile ton exemple. J'ai réussi comme ca :

g++ stdatomic.cpp -std=c++0x -pthread

A bientôt,
Ingo

M87 a dit…

Salut Ingo!

En effet, j'avais oublié de mettre la ligne de compile... Bien vu, et merci!

Anonyme a dit…

Ça ne sera pas forcément 0, mais la valeur à laquelle la variable a été affectée lors de son initialisation lors de l'exécution (un test sur mon système, gcc 4.6.x me l'a prouvé). Car autant que je sache, même le nouveau standard ne définit pas de valeur d'initialisation pour les types définis par défaut.

M87 a dit…

Bien vu, j'ai en effet oublié d'initialiser mon compteur.