2016-01-20 2 views
1

У меня есть две темы, Продюсер и Потребитель. Обмен данными осуществляется с помощью двух указателей внутри STD :: Атомикс:Как заставить std :: atomic читать после того, как был записан другой std :: atomic?

std::atomic<TNode*> next = nullptr; 
std::atomic<TNode*> waiting = nullptr; 

Производитель Thread publishs подготовленные данные и после проверяет значение ожидания:

TNode* newNext = new TNode(); 
// ... fill *newNext ... 
next.store(newNext, std::memory_order_release); 
TNode* oldWaiting = waiting.load(std::memory_order_seq_cst); 
if(oldWaiting == nullptr) 
{ 
    /* wake up Consumer */ 
} 

Это crucical, что нагрузка на waiting приходит после магазина на next, но std::memory_order_seq_cst имеет значительно более надежные гарантии, чем мне действительно нужно, так как мне действительно нужен только порядок этих двух доступов. Возможно ли получить заказ на память, который мне нужен, без необходимости в memory_order_seq_cst?

Вот остальная часть картины:

Thread Потребительские проверки next. Если он находит его пустым, он устанавливает waiting, чтобы сигнализировать производителю перед его блокировкой.

TNode* newCurrent = next.load(std::memory_order_consume); 
if(newCurrent == nullptr) 
{ 
    waiting.store(current, std::memory_order_relaxed); 
    /* wait, blocking, for next != nullptr */ 
} 
current = newCurrent; 

Все это очередь производителей-потребителей, которая сохраняет потребность в блокировке с низким уровнем, не требуя всех сложных механизмов. next фактически находится внутри текущего узла односвязного списка. Данные обычно поступают в пакеты, так что в большинстве случаев потребитель находит целую кучу узлов, готовых к потреблению; кроме редких случаев, оба потока и только проходят через блокировку и блокировку/пробуждение один раз между очередями.

ответ

-2

короткий ответ:

Компилятор волен изменить порядок доступа к памяти (или даже игнорировать их, когда не летучий), за исключением:

Если вы указываете магазин, чтобы быть memory_order_release до нагрузки с указанием memory_order_acquire, то компилятор должен уважать ваше намерение и не изменять порядок загрузки до «магазина».

Последовательная консистенция будет достигать этого, не принося головные боли сопровождающим. Он также оптимально эффективен на предстоящем плече 8, который будет первым процессором, который будет правильно реализован при загрузке и хранении.

Вы можете найти все, что вам когда-либо нужно знать о C++ Атомикса в этих двух переговорах:

https://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-1-of-2

https://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-2-of-2

Я хотел бы предложить, что они необходимы осмотр, прежде чем пытаться что-либо с Атомиксом.

После того, как вы их просмотрите, вы, вероятно, поймете, что раньше ничего не знали об атоматике, хотя и считали, что это так. Это, безусловно, было для меня.

1

Вы по существу ищете порядок зеркала memory_order_release. Это memory_order_acquire.

Это немного сильнее, чем вы просите. После .load доступ к памяти не может быть переупорядочен.Тем не менее, CPU вообще не предлагают способ частично заказать два доступа, поэтому C++ поэтому не имеет такой степени детализации.

C++ в теории имеет также release/consume, но никто этого не требует.

+0

Как я понимаю, 'memory_order_acquire' ограничивает другие чтения, которые нужно переупорядочить перед загрузкой, но не накладывает никаких ограничений на записи, ни предотвращает их перемещение, читает после загрузки. Или я ошибаюсь? –

+0

Откладывание грузов проще всего понять. Компилятор всегда может задерживать другие нагрузки (при условии, что они не секвенированы сами). Эта задержка может дать только другой результат, если другой поток записывает новые значения в переменную, чья нагрузка задерживается, что означает, что у вас есть условие гонки в любом случае. Заказ памяти не имеет смысла при наличии такого Неопределенного Поведения. Я не думаю, что случайная запись повредит, _unless_, что запись подхватывается другим потоком, используемым в вычислении, а затем используется в вычислении, которое влияет на загрузку с помощью семантики получения. И тогда у вас есть зависимость. – MSalters

2

Будьте очень осторожны. Вы будете нуждаться в разделительном заборе и заборе где сбор по, чтобы убедиться, что запись вы выполненной во:

TNode* newNext = new TNode(); 
// ... fill *newNext ... 

Видим потребителю.

Ближайшие, что вы можете сделать, это выполнить «расслабленное» чтение атома в Потребителе , затем выполнить приобретение и начать «поглощать» объект. На некоторых (наиболее?) Архитектурах, которые, вероятно, не будут иметь никакого эффекта.

Прочтите «Пошаговое руководство, используя захват и выпуск ограждений» здесь http://preshing.com/20130922/acquire-and-release-fences/.

Я не мог написать что-то ближе к работаемому примеру именно того, что вы делаете. Продюсер/Потребитель (сталкивается с этим) - вызов учебника.

Немного о проблеме. Я бы использовал std::condition_variable. Они созданы для этого.

Чуть дальше от вопроса Я не слишком увлечен вашей стратегией блокировки. Это зависит от того, сколько времени может потребоваться производитель/потребитель, но если производитель «всплески», как вы говорите, это может быть плохая идея заблокировать его. Белый потребитель работает. Вы эффективно сделали их по очереди. Что вы можете сделать (только с небольшим вниманием) - это сделать Продюсера способным работать на (TNodes) на обратной стороне очереди, практически беспрепятственно, от Потребителя. Поэтому, если потребитель занимает некоторое время, Продюсер не может составлять никаких накладных расходов.

Это сделать дизайн, который не имеет:

/* wait, blocking, for next != nullptr */ 

Это держит до

TNode* newNext = new TNode(); 
// ... fill *newNext ... 

На следующий рабочий элемент. NB: Если потребителю логически нужно закончить, прежде чем это произойдет, вся идея параллелизма для этой задачи будет перегружена, и вы также можете пойти последовательно.

+0

Извините за путаницу. 'next' фактически находится внутри' TNode'. Продюсер и Потребитель каждый отслеживают, на каком узле они работают, и продвигаются независимо, поэтому продюсер может хранить добавочные узлы, не блокируя их. Только потребитель будет блокировать, и только если нет готового узла (то есть 'next' не был установлен производителем в текущем узле Потребителя). Я использую 'std :: condition_variable' для блокирующей части; но поскольку это очень настроенная реализация, я не использую ее для каждого узла. И да, я осторожен.Я бы не попытался это сделать, если бы я не учился, как годами. –

Смежные вопросы