Можно ли использовать std :: atomic со структурой, которая является POD, за исключением того, что у него есть construtor? - PullRequest
0 голосов
/ 30 мая 2018

Я использую несколько атомарных переменных, все беззнаковые целые, и я хотел собрать их в структуру - фактически POD.Однако я также хочу конструктор, потому что мой компилятор не совсем c ++ 11 (поэтому я должен определить свой собственный конструктор, чтобы создать его с начальными значениями).

Итак, изначально у меня было:

// Names are not the real names - this is just for example
std::atomic<int> counter1;
std::atomic<int> counter2;
std::atomic<int> counter3;

А потом я был рад просто увеличивать / уменьшать их по мере необходимости.Но затем я решил, что мне нужно еще несколько счетчиков и, следовательно, поместить их в структуру:

struct my_counters {
    int counter1;
    int counter2;
    int counter3;
    // Constructor so that I can init the values I want.
    my_counters(c1, c2, c3) : counter1(c1), counter2(c2), counter3(c3){;}
};

Но поскольку я добавил пользовательский конструктор, технически он больше не является POD.Я читал другие вопросы, касающиеся этого, и они говорили, что для использования std :: atomic мне нужен POD, но другие вопросы, которые я читал, предполагали, что структура должна быть копируемой или что-то подобное ... в любом случае, я запутался и хочучтобы узнать, могу ли я безопасно использовать мою структуру my_counters в качестве атомарного типа:

std::atomic<my_counters> counters;

И затем в различных потоках:

// Are these operations now still atomic (and therefore safe to use across threads):
counters.counter1++;
counters.counter2--;
counters.counter3 += 4;

Ответы [ 2 ]

0 голосов
/ 30 мая 2018

В большинстве случаев std::atomic не имеет смысла для структур, потому что вы в конечном итоге будете копировать всю структуру для каждого изменения:

std::atomic<my_counters> var(1,2,3);
my_counters another_var = var.load(); // atomic copying
another_var.counter1++;
var.store(another_var); // atomic copying

Более того, load и store являются отдельными операциями, поэтомумы не можем гарантировать, что var.counter1 равно 3 для двух потоков, выполняющих приведенный выше код.

Кроме того, если ваш целевой ЦП не поддерживает атомарные операции для структур такого размера, std::atomic откатитсядля использования мьютекса:

#include <atomic>
#include <iostream>

struct counters {
    int a;
    int b;
    int c;
};

int main() {
    std::atomic<counters> c;
    std::atomic<int> a;

    std::cout << std::boolalpha << c.is_lock_free() << std::endl;
    std::cout << std::boolalpha << a.is_lock_free() << std::endl;
    return 0;
}

Демо

В демоверсии вы можете увидеть, что std::atomic<counters> использует мьютекс для внутреннего использования.

Итак, вам лучше иметь std::atomic<int> в качестве учеников, как Павел предлагает.

0 голосов
/ 30 мая 2018

Другие говорили это, но просто для ясности, я думаю, вам нужно это:

struct my_counters {
    std::atomic<int> counter1;
    std::atomic<int> counter2;
    std::atomic<int> counter3;
    // Constructor so that I can init the values I want.
    my_counters(c1, c2, c3) : counter1(c1), counter2(c2), counter3(c3){;}
};

А потом просто:

my_counters counters;

Другими словами, это счетчикикоторые являются атомарными, а не структурой.Структура просто служит для их группировки и инициализации.

Edit by Peter

Если вы используете эти счетчики из разных потоков одновременно, вы можете захотетьизбегайте ложного разделения между потоками, помещая каждый счетчик в отдельную строку кэша.(Обычно 64 байта).Вы можете использовать C ++ 11 alignas на элементах, чтобы ваш компилятор дополнял структуру структуры, или вручную вставлять несколько фиктивных char padding[60] элементов между каждым atomic.

Редактировать мной

Хорошая ссылка о понимании кэша в целом здесь .Стоит прочтения.Похоже, что в наши дни строки кэша Intel составляют 64 байта из-за быстрого поиска в Google, но не цитируйте меня.

Еще одно редактирование мной

МногоВ комментариях ниже было сказано о плюсах и минусах использования std :: atomic для присмотра (произвольного) класса или структуры, например,

struct MyStruct
{

    int a;
    int b;
};

std::atomic<MyStruct> foo = { };

Но у меня такой вопрос: когда это когда-либо будет полезно? В частности, как указывает ivaigult, вы не можете использовать std :: atomic для изменения отдельных элементов MyStruct потокобезопасным способом.Вы можете использовать его только для загрузки, хранения или обмена всего, и желание делать это не так уж часто.

Единственный законный вариант использования, о котором я могу подумать, это когда вы хотите иметь возможность поделиться чем-то вроде(например) struct tm между потоками таким образом, чтобы поток никогда не видел его в несогласованном состоянии.Затем, если структура мала, вы можете обойтись без блокировки на вашей конкретной платформе, и это полезно.Просто помните о последствиях (инверсия приоритетов является наиболее серьезной для кода реального времени), если вы не можете.

Если вы do хотите разделить struct между потоками и бытьвозможность обновлять отдельные элементы потокобезопасным способом, тогда std::atomic не обрезает его (и не было предназначено для этого).Затем вам нужно прибегнуть к мьютексу, и для этого удобно извлечь свою структуру из std::mutex следующим образом:

struct AnotherStruct : public std::mutex
{

    int a;
    int b;
};

И теперь я могу сделать (например):

AnotherStruct bar = { };

bar.lock ().
bar.a++;
bar.b++;
bar.unlock ();

Это позволяет вам обновлять две (предположительно каким-то образом связанные) переменные потокобезопасным способом.

Извините, если все это очевидно для более опытных участников кампании, ноЯ хотел уточнить вещи в моем собственном уме.На самом деле это не имеет ничего общего с вопросом ОП.

...