По непонятным причинам я был уверен, что static volatile int является атомарным счетчиком. Вроде бы достаточно логично — оптимизация запрещена, чтение и запись должны быть сразу в оперативную память, работать все должно быстро. Как оказалось нет.
Одно из выступлений с cppcon, где один из разработчиков clang доступно объясняет, что вообще никто не знает что делает volatile:
Пришлось разобраться и проверить.
#include <stdio.h>
#include <thread>
#include <atomic>
std::atomic<int> acnt;
static volatile int vcnt;
const int ub_cnt = 0;
int cnt;
int f(int &lcnt, const int &local_const_cnt)
{
for(int i = 0; i < 100000; i++) {
++cnt;
++acnt;
++vcnt;
++lcnt;
++(*const_cast<int*>(&ub_cnt)); // this will crash the program with -O0
++(*const_cast<int*>(&local_const_cnt));
}
return 0;
}
int main(void)
{
int lcnt = 0;
const int local_const_cnt = 0;
std::thread* thr[1000];
for(int i = 0; i < 1000; i++)
thr[i] = new std::thread(f, std::ref(lcnt), std::ref(local_const_cnt));
for (int i = 0; i < 1000; i++)
thr[i]->join();
printf("The atomic counter is %u\n", acnt.load());
printf("The non-atomic counter is %u\n", cnt);
printf("Volatile counter is %u\n", vcnt);
printf("Local counter is %u\n", lcnt);
printf("Local const counter is %u\n", local_const_cnt);
printf("UB counter is %u\n", ub_cnt);
// next line will crash the program because of undefined behaviour on any optimization level
// printf("UB counter force load is %u\n", *const_cast<const volatile int*>(&ub_cnt));
}
Достаточно смешные результаты, плавающие от оптимизации (на -O0 ub_cnt я убрал):
vlad@vtyulb-thinkpad ~ % g++ main.cpp -lpthread -O0 vlad@vtyulb-thinkpad ~ % ./a.out The atomic counter is 100000000 The non-atomic counter is 993844 Volatile counter is 912750 Local counter is 1815379 Local const counter is 0 UB counter is 0 ./a.out 36,80s user 0,08s system 766% cpu 4,812 total vlad@vtyulb-thinkpad ~ % ./a.out The atomic counter is 100000000 The non-atomic counter is 1073648 Volatile counter is 938156 Local counter is 2031276 Local const counter is 0 UB counter is 0 ./a.out 37,19s user 0,19s system 757% cpu 4,933 total vlad@vtyulb-thinkpad ~ % g++ main.cpp -lpthread -O1 vlad@vtyulb-thinkpad ~ % ./a.out The atomic counter is 100000000 The non-atomic counter is 27314903 Volatile counter is 1159589 Local counter is 36943434 Local const counter is 0 UB counter is 0 ./a.out 36,77s user 0,14s system 764% cpu 4,829 total vlad@vtyulb-thinkpad ~ % ./a.out The atomic counter is 100000000 The non-atomic counter is 27600981 Volatile counter is 769927 Local counter is 35498305 Local const counter is 0 UB counter is 0 ./a.out 38,26s user 0,08s system 763% cpu 5,019 total vlad@vtyulb-thinkpad ~ % g++ main.cpp -lpthread -O2 vlad@vtyulb-thinkpad ~ % ./a.out The atomic counter is 100000000 The non-atomic counter is 24814898 Volatile counter is 857348 Local counter is 52322802 Local const counter is 0 UB counter is 0 ./a.out 41,11s user 0,10s system 765% cpu 5,385 total vlad@vtyulb-thinkpad ~ % ./a.out The atomic counter is 100000000 The non-atomic counter is 24785663 Volatile counter is 850257 Local counter is 51293639 Local const counter is 0 UB counter is 0 ./a.out 40,76s user 0,13s system 767% cpu 5,329 total vlad@vtyulb-thinkpad ~ % g++ main.cpp -lpthread -O3 vlad@vtyulb-thinkpad ~ % ./a.out The atomic counter is 100000000 The non-atomic counter is 25486880 Volatile counter is 800002 Local counter is 55676774 Local const counter is 0 UB counter is 0 ./a.out 39,96s user 0,11s system 761% cpu 5,263 total vlad@vtyulb-thinkpad ~ % ./a.out The atomic counter is 100000000 The non-atomic counter is 25704519 Volatile counter is 686848 Local counter is 54817634 Local const counter is 0 UB counter is 0 ./a.out 39,46s user 0,15s system 757% cpu 5,229 total
Вообще, недавно прочитал почти весь блог одного товарища, начав с этой статьи http://scrutator.me/post/2015/04/05/cpu_memory_inter_multiprocessor.aspx
Это практически полностью перевернуло мое понимание многопоточного доступа к памяти, потому что раньше я считал барьеры памяти теоретической бесполезной фигней, а они оказывается действительно существуют: https://en.cppreference.com/w/cpp/atomic/atomic_thread_fence