Как использовать std:: atomic <>
У меня есть класс, который я хочу использовать в разных потоках, и я думаю, что смогу использовать std:: atomic, например:
classs A
{
int x;
public:
A()
{
x=0;
}
void Add()
{
x++;
}
void Sub()
{
x--;
}
};
и в моем коде:
std::atomic<A> a;
и в другом потоке:
a.Add();
и
a.Sub();
но когда я получаю ошибку, что a.Add() неизвестно. Как я могу достичь этого?
Есть ли лучший способ сделать это?
Изменить 1
Обратите внимание, что это примерный пример, и я хочу, чтобы доступ к классу A был потокобезопасным, поэтому я не могу использовать
std::atomic<int> x;
Как я могу создать потоки классов с помощью std:: atomic?
Ответы
Ответ 1
Вам нужно сделать атрибут x
атомарным, а не всем вашим классом, следующим образом:
class A
{
std::atomic<int> x;
public:
A() {
x=0;
}
void Add() {
x++;
}
void Sub() {
x--;
}
};
Ошибка, которую вы получаете в исходном коде, абсолютно нормальная: нет метода std::atomic<A>::Add
(см. здесь), если вы не предоставите специализацию для std::atomic<A>
.
Ссылаясь на ваше редактирование: вы не можете магически сделать поток class A
безопасным, используя его как аргумент шаблона std::atomic
. Чтобы сделать его потокобезопасным, вы можете сделать его атрибуты атомарными (как было предложено выше, и при условии, что стандартная библиотека дает для него специализацию) или использовать мьютексы для блокировки ваших ресурсов самостоятельно. См. Заголовок mutex. Например:
class A
{
std::atomic<int> x;
std::vector<int> v;
std::mutex mtx;
void Add() {
x++;
}
void Sub() {
x--;
}
/* Example method to protect a vector */
void complexMethod() {
mtx.lock();
// Do whatever complex operation you need here
// - access element
// - erase element
// - etc ...
mtx.unlock();
}
/*
** Another example using std::lock_guard, as suggested in comments
** if you don't need to manually manipulate the mutex
*/
void complexMethod2() {
std::lock_guard<std::mutex> guard(mtx);
// access, erase, add elements ...
}
};
Ответ 2
Объявить член класса x как атомный, тогда вам не нужно объявлять объект как атомный
class A
{
std::atomic<int> x;
}
Ответ 3
Оператор .
может использоваться для объекта, чтобы вызвать его функцию-член класса, а не какую-либо другую функцию-член класса (если вы явно не пишете код таким образом).
std::atomic<A> a ;
a.Add(); // Here, a does not know what Add() is (a member function of the type parameter)
// It tries to call Add() method of its own class i.e. std::atomic
// But std::atomic has no method names Add or Sub
Как следует из ответа @ivanw, сделайте std::atomic<int>
членом своего класса, а затем используйте его.
Вот еще один пример:
template <typename T> class A
{};
class B { public: void hello() { std::cout << "HELLO!!!"; } };
A<B> a ;
a.hello(); // This statement means that call a hello member function
// But the typeof(a) which is A does not have such a function
// Hence it will be an error.
Ответ 4
Я думаю, что проблема с вышеприведенными ответами заключается в том, что они не объясняют, что я думаю, как минимум, двусмысленностью в вопросе и, скорее всего, общей ошибочной ошибкой разработки.
Вы не можете сделать объект "атомарным", потому что интервал между двумя функциями (сначала "read x", а затем "write x" ) вызовет гонку с другими видами использования. Если вы считаете, что вам нужен "атомный" объект, тогда вам нужно тщательно разработать API и функции-члены, чтобы выставлять сейчас, чтобы начинать и фиксировать обновления для объекта.
Если все, что вы имеете в виду под "атомарным", это "объект не повреждает его внутреннее состояние", тогда вы можете достичь этого через std::atomic<>
для одиночных типов данных простого старого типа, которые не имеют инварианта между ними (a doesn 't зависит от b), но вам нужна какая-либо блокировка для любых зависимых правил, которые вам необходимо выполнить.