Je n'ai pas de liens vers la norme. Je l'ai vérifiée il y a longtemps, std::shared_ptr
est thread-safe sous certaines conditions, qui se résument à : chaque thread doit avoir sa propre copie. Comme documenté sur Référence cpp :
Toutes les fonctions membres (y compris le constructeur de copie et l'affectation de copie) peuvent être appelées par plusieurs threads sur différentes instances de shared_ptr sans synchronisation supplémentaire, même si ces instances sont des copies et partagent la propriété du même objet. Si plusieurs threads d'exécution accèdent au même shared_ptr sans synchronisation et que l'un de ces accès utilise une fonction membre non-const de shared_ptr, une course aux données se produira.
Ainsi, comme pour toute autre classe de la norme, la lecture de la même instance à partir de plusieurs threads est autorisée. L'écriture dans cette instance à partir d'un seul thread ne l'est pas.
int main()
{
std::vector<std::thread> threads;
{
// A new scope
// So that the shared_ptr in this scope has the
// potential to go out of scope before the threads have executed.
// So leaving the shared_ptr in the scope of the threads only.
std::shared_ptr<int> data = std::make_shared<int>(5);
// Perfectly legal to read access the shared_ptr
threads.emplace_back(std::thread([&data]{ std::cout << data.get() << '\n'; }));
threads.emplace_back(std::thread([&data]{ std::cout << data.get() << '\n'; }));
// This line will result in a race condition as you now have read and write on the same instance
threads.emplace_back(std::thread([&data]{ data = std::make_shared<int>(42); }));
for (auto &thread : threads)
thread.join();
}
}
Une fois que nous avons affaire à des copies multiples du shared_ptr, tout va bien :
int main()
{
std::vector<std::thread> threads;
{
// A new scope
// So that the shared_ptr in this scope has the
// potential to go out of scope before the threads have executed.
// So leaving the shared_ptr in the scope of the threads only.
std::shared_ptr<int> data = std::make_shared<int>(5);
// Perfectly legal to read access the shared_ptr copy
threads.emplace_back(std::thread([data]{ std::cout << data.get() << '\n'; }));
threads.emplace_back(std::thread([data]{ std::cout << data.get() << '\n'; }));
// This line will no longer result in a race condition the other threads are using a copy
threads.emplace_back(std::thread([&data]{ data = std::make_shared<int>(42); }));
for (auto &thread : threads)
thread.join();
}
}
La destruction du shared_ptr se fera également sans problème, car chaque thread appellera le destructeur du shared_ptr local et le dernier nettoiera les données. Il y a quelques opérations atomiques sur le compte de référence pour s'assurer que cela se passe correctement.
int main()
{
std::vector<std::thread> threads;
{
// A new scope
// So that the shared_ptr in this scope has the
// potential to go out of scope before the threads have executed.
// So leaving the shared_ptr in the scope of the threads only.
std::shared_ptr<int> data = std::make_shared<int>(5);
// Perfectly legal to read access the shared_ptr copy
threads.emplace_back(std::thread([data]{ std::cout << data.get() << '\n'; }));
threads.emplace_back(std::thread([data]{ std::cout << data.get() << '\n'; }));
// Sleep to ensure we have some delay
threads.emplace_back(std::thread([data]{ std::this_thread::sleep_for(std::chrono::seconds{2}); }));
}
for (auto &thread : threads)
thread.join();
}
Comme vous l'avez déjà indiqué, l'accès aux données dans le shared_ptr n'est pas protégé. Donc, comme dans le premier cas, si vous avez un thread qui lit et un thread qui écrit, vous avez toujours un problème. Cela peut être résolu avec des atomiques ou des mutex ou en garantissant la lecture seule des objets.