Похоже, что вы переназначаете shared_ptr
, который разделяется между потоками:
_main_data = _back_data;
Если другой поток читает или копирует _main_data
одновременно, он может получить поврежденную копию.
Назначение shared_ptr
не является потокобезопасным, поскольку shared_ptr
содержит два члена-указателя, и они не могут быть обновлены атомарно. См shared_ptr
:
Если несколько потоков выполнения обращаются к одному и тому же shared_ptr
без синхронизации и любой из этих обращений использует неконстантную функцию-член shared_ptr
, тогда произойдет гонка данных;
Чтобы исправить это состояние гонки, код должен использовать atomic_store
:
atomic_store(&_main_data, _back_data);
И читатели должны сделать:
auto main_data = atomic_load(&_main_data);
Раздел примечаний полезно:
Эти функции обычно реализуются с использованием мьютексов, которые хранятся в глобальной хеш-таблице, где в качестве ключа используется значение указателя.
Чтобы избежать гонок данных, после того, как общий указатель передан какой-либо из этих функций, к нему нельзя получить доступ не атомарно. В частности, вы не можете разыменовать такой shared_ptr
без предварительной атомной загрузки его в другой объект shared_ptr
, а затем разыменования через второй объект.
Concurrency TS предлагает атомные классы интеллектуальных указателей atomic_shared_ptr
и atomic_weak_ptr
в качестве замены для использования этих функций.
Начиная с C ++ 20: Эти функции устарели в пользу специализаций шаблона std::atomic
: std::atomic<std::shared_ptr>
и std::atomic<std::weak_ptr>
.
Кроме того, вы должны заставить Data
деструктор выполнять всю очистку, чтобы вам не приходилось ждать, пока потоки считывателя не выпустят _main_data
, чтобы очистить его вручную.
Кроме того, вы можете использовать std::atomic
и boost::intrusive_ptr
, чтобы сделать обновление указателя данных поточно-безопасным, атомарным, без ожидания и без утечек.
Преимущество использования boost::intrusive_ptr
вместо std::shared_ptr
состоит в том, что первое может быть безопасно создано потоком из простого указателя, поскольку атомный счетчик ссылок хранится внутри объекта.
Рабочий пример:
#include <iostream>
#include <atomic>
#include <boost/smart_ptr/intrusive_ptr.hpp>
#include <boost/smart_ptr/intrusive_ref_counter.hpp>
struct Data
: boost::intrusive_ref_counter<Data, boost::thread_safe_counter>
{};
using DataPtr = boost::intrusive_ptr<Data>;
class DataAccessor
{
std::atomic<Data*> data_ = 0;
public:
~DataAccessor() {
DataPtr{data_.load(std::memory_order_acquire), false}; // Destroy data_.
}
DataPtr get_data() const {
return DataPtr{data_.load(std::memory_order_acquire)};
};
void set_data(DataPtr new_data) {
DataPtr old_data{data_.load(std::memory_order_relaxed), false}; // Destroy data_.
data_.store(new_data.detach(), std::memory_order_release);
}
};
int main() {
DataAccessor da;
DataPtr new_data{new Data};
da.set_data(new_data);
DataPtr old_data = da.get_data();
std::cout << (new_data == old_data) << '\n';
}
valgrind
пробег:
$ valgrind ./test
==21502== Memcheck, a memory error detector
==21502== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==21502== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==21502== Command: ./test
==21502==
1
==21502==
==21502== HEAP SUMMARY:
==21502== in use at exit: 0 bytes in 0 blocks
==21502== total heap usage: 4 allocs, 4 frees, 73,736 bytes allocated
==21502==
==21502== All heap blocks were freed -- no leaks are possible
==21502==
==21502== For counts of detected and suppressed errors, rerun with: -v
==21502== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)