Этот код имеет гонку данных в steal
и, следовательно, неопределенное поведение, независимо от порядка памяти.
Ничто не мешает потоку кражи, вызывающему getItemAt(top)
, прочитать значение по заданному индексу, в то время как рабочий поток, которому принадлежит очередь, вызывает push
достаточное количество раз, чтобы обернуться вокруг буфера и перезаписать запись, или вызывает pop
достаточно раз для очистки очереди, а затем вызывает push
для перезаписи этой записи.
например. mTop
равно 0, mBottom
равно 1 => очередь имеет один элемент.
Кража потока читает mTop
и mBottom
. top<bottom
, поэтому он получает на вызов getItemAt(top)
и приостанавливается ОС из-за переключения задач.
Рабочий поток вызывает pop
. Он читает mBottom
и устанавливает bottom
в 0. Затем он читает top
(0). 0==0
, поэтому мы вызываем getItemAt(bottom)
, чтобы получить элемент. Затем он увеличивает mTop
до 1 и устанавливает mBottom
в 1.
Рабочий поток затем вызывает push
и вызывает setItemAt(mBottom)
, чтобы установить следующий элемент, который теперь является элементом 1.
Рабочий поток теперь повторяет этот push
/ pop
танец COUNT
раз, поэтому в очереди никогда не бывает более одного элемента, но каждый раз увеличивается mTop
и mBottom
, поэтому активный элемент перемещается вокруг буфер до mBottom & MASK
снова 0.
Рабочий поток вызывает push
и, следовательно, setItemAt(mBottom)
, который обращается к элементу 0. ОС возобновляет поток кражи, который также обращается к элементу 0 => для чтения и записи в том же месте без упорядочивания => гонки данных и неопределенное поведение.
Это нормально только если TYPE
равно std::atomic<T>
для некоторых T
.
Если предположить, что COUNT
достаточно велико, чтобы на практике этого никогда не происходило, тогда push
записывает в mBottom
с memory_order_release
, а steal
читает с memory_order_acquire
. Это означает, что запись в соответствующий элемент данных происходит до чтения элемента в steal
, поэтому чтение элемента в порядке. Это видно даже при использовании fetch_sub
в pop
с использованием memory_order_relaxed
из-за концепции, называемой «последовательность выпусков».
Использование memory_order_seq_cst
для нагрузок и успешный сравнительный обмен mTop
вынуждают операции на mTop
в единый глобальный общий заказ. Однако комментарий о загрузке mTop
в pop
неверен: использование memory_order_seq_cst
не препятствует переупорядочению вызова mBottom.fetch_sub
, так как это load
из mTop
, а fetch_sub
вызов использует memory_order_relaxed
. memory_order_seq_cst
в load
не налагает никакого упорядочения на записи не-1076 * из того же потока в другие переменные.
В настоящее время я не уверен, какое влияние это может оказать на код.