Прочитав главу 5 «Параллелизм в C ++ в действии», я попытался написать код, чтобы проверить свое понимание упорядочения памяти:
#include <iostream>
#include <vector>
#include <thread>
#include <atomic>
std::atomic<int> one,two,three,sync;
void func(int i){
while(i != sync.load(std::memory_order_acquire));
auto on = one.load(std::memory_order_relaxed); ++on;
auto tw = two.load(std::memory_order_relaxed); ++tw;
auto th = three.load(std::memory_order_relaxed); ++th;
std::cout << on << tw << th << std::endl;
one.store(on,std::memory_order_relaxed);
two.store(tw,std::memory_order_relaxed);
three.store(th,std::memory_order_relaxed);
int expected = i;
while(!sync.compare_exchange_strong(expected,i+1,
std::memory_order_acq_rel))
expected = i;
}
int main(){
std::vector<std::thread> t_vec;
for(auto i = 0; i != 5; ++i)
t_vec.push_back(std::thread(func,i));
for(auto i = 0; i != 5; ++i)
t_vec[i].join();
std::cout << one << std::endl;
std::cout << two << std::endl;
std::cout << three << std::endl;
return 0;
}
Мой вопрос: в книге говорится, что memory_order_release и memory_order_acquire должны быть парой, чтобы правильно читать правильное значение.
Поэтому, если первая строка func () - это синхронизация загрузки в цикле с memory_order_acquire, она должна разорвать пару и сделать непредсказуемую ошибку при синхронизации.
Однако, как и ожидалось, он печатает после компиляции на моей платформе x86:
111
222
333
444
555
5
5
5
Результат не показывает никаких проблем. Так что мне просто интересно, что происходит внутри func () (хотя я написал это сам ...)?
Добавлено: согласно коду C ++ на странице 141 параллелизма в действии:
#include <atomic>
#include <thread>
std::vector<int> queue_code;
std::atomic<int> count;
void populate_queue(){
unsigned const number_of_items = 20;
queue_data.clear();
for(unsigned i = 0; i < number_of_items; ++i)
queue_data.push_back(i);
count.store(number_of_items, std::memory_order_release);
}
void consume_queue_items(){
while(true){
int item_index;
if((item_index=count.fetch_sub(1,memory_order_acquire))<=0){
wait_for_more_items();
continue;
}
process(queue_data[item_index-1]);
}
}
int main(){
std::thread a(populate_queue);
std::thread b(consume_queue_items);
std::thread c(consume_queue_items);
a.join();
b.join();
c.join();
}
Поток b и поток c будут работать нормально, независимо от того, кто обращается первым. Потому что:
К счастью, первая функция fetch_sub () действительно участвует в последовательности release , поэтому store () синхронизируется со второй fetch_sub (). Между двумя потоками-потребителями по-прежнему нет взаимосвязи «синхронизируется с». В цепочке может быть любое количество ссылок, но при условии, что они все операции чтения-изменения-записи, такие как fetch_sub (), store () все равно будет синхронизироваться с каждая из них помечена как memory_order_acquire. В этом примере все ссылки одинаковы и все являются операциями получения, но они могут быть смесью разных операций с разной семантикой memory_ordering.
Но я не могу найти соответствующую информацию об этом, и как операции чтения-изменения-записи, такие как fetch_sub (), участвуют в последовательности выпуска? Если я изменю его на загрузку с помощью memory_order_acquire, будет ли store () по-прежнему синхронизироваться с load () в каждом независимом потоке?