- 01
- 02
- 03
- 04
- 05
- 06
- 07
- 08
- 09
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
#include <cstdlib>
#include <chrono>
#include <iostream>
#include <thread>
int p = 0;
int *q = nullptr;
void g()
{
using namespace std::chrono_literals;
std::cout << "g()" << std::endl;
std::cout << "g(): p = 1" << std::endl;
p = 1;
std::this_thread::sleep_for(1s);
if (q != nullptr) {
std::cout << "g(): *q = 1" << std::endl;
*q = 1;
} else {
std::cout << "g(): q == nullptr" << std::endl;
}
}
void f()
{
using namespace std::chrono_literals;
std::cout << "f()" << std::endl;
if (p == 0) {
std::cout << "f(): first loop start" << std::endl;
while (p == 0) { } // Потенциально конечный
std::cout << "f(): first loop end" << std::endl;
}
int i = 0;
q = &i;
std::cout << "f(): second loop start" << std::endl;
while (i == 0) { } // Потенциально конечный, хотя в условии только автоматическая пельменная
std::cout << "f(): second loop end" << std::endl;
}
int main()
{
using namespace std::chrono_literals;
std::cout << "f() thread start" << std::endl;
auto thr1 = std::thread(f);
thr1.detach();
std::this_thread::sleep_for(1s);
std::cout << "g() thread start" << std::endl;
auto thr2 = std::thread(g);
thr2.detach();
std::this_thread::sleep_for(2s);
std::cout << "Done" << std::endl;
std::_Exit(EXIT_SUCCESS);
}
gost 28.09.2020 12:52 # 0
Реальный пример: https://ideone.com/OCJQg9 («Wandbox» лежит, зараза).
А чтобы получить ожидаемый вывод — надо конпелировать с «-O0».
Выхлоп компилятора прекрасен (чуть упрощённый кот): https://gcc.godbolt.org/z/Kx4xeE
gost 28.09.2020 13:24 # 0
1.10 Multi-threaded executions and data races
The implementation may assume that any thread will eventually do one of the following:
— terminate,
— make a call to a library I/O function,
— access or modify a volatile object, or
— perform a synchronization operation or an atomic operation.
В f() нет ни I/O, ни доступа к волатильным объектам, ни атомарных операций.
Какой карманный лев )))
bormand 28.09.2020 13:29 # +1
Это на x86 мы привыкли к полной когерентности, а есть ведь платформы с более слабой моделью памяти, где проц тупо не будет перезагружать кешлайн, в котором лежит p или i. Ты ведь не дал ему такого повода.
guest8 28.09.2020 14:53 # −999
3.14159265 28.09.2020 13:56 # 0
Myxa 28.09.2020 13:43 # 0
bormand 28.09.2020 13:44 # +1
Myxa 28.09.2020 13:44 # 0
bormand 28.09.2020 13:45 # 0
Desktop 28.09.2020 13:47 # 0
bormand 28.09.2020 13:48 # +1
Это важно для обращения ко всяким железкам, когда порядок записей и даже чтений имеет значение.
Ну и в обработчике прерываний, если у тебя с ним какие-то переменные расшарены.
guest8 28.09.2020 14:54 # −999
3.14159265 28.09.2020 15:04 # +1
Every access (read or write operation, member function call, etc.) made through a glvalue expression of volatile-qualified type is treated as a visible side-effect for the purposes of optimization (that is, within a single thread of execution, volatile accesses cannot be optimized out or reordered with another visible side effect that is sequenced-before or sequenced-after the volatile access. This makes volatile objects suitable for communication with a signal handler, but not with another thread of execution
Опять же, я считаю это не совсем правдой. volatile можно использовать для синхронизации если старая-добрая одноядерная машина.
>у нас в жабе всё проще
Я бы не сказал. Оно везде сложно.
Они в 8ой вроде fence из крестов навезли.
guest8 28.09.2020 15:09 # −999
bormand 28.09.2020 15:12 # 0
3.14159265 28.09.2020 15:16 # 0
Они из java memory model его и заимствовали :)
Но в лучших традициях С++, они поступили согласно принципу: давайте возьмём концепт и сделаем его в 10 раз сложнее.
Просто в Йажа volatile был full fence. Крестобляди рассудили что это черезчур медленно и черезчур просто и добавили 4 разных memory_order.
guest8 28.09.2020 15:23 # −999
3.14159265 28.09.2020 15:53 # 0
Но она хуёво ложится на разнообразие железа и низкоуровневых инструкций. (MFENCE, LFENCE, SFENCE)
И не всегда имеет оптимальный пирформанс, накладывая своими гарантиями лишние ограничения на компилятор.
Во многих случаях достаточно memory_order_relaxed.
Именно поэтому я за «C++».
MAPTbIwKA 28.09.2020 15:55 # 0
угу)
ну это же вечный трейдофф: "много думать" versus тормоза.
Вот в питоне люди юзают GIL, и им куда проще жить
MAPTbIwKA 28.09.2020 15:17 # 0
когда вообещ в кресты завезли мемори модел для тредов? C++11?
bormand 28.09.2020 15:17 # 0
> если старая добрая одноядерная машина
Нет. Разве что в примитивных случаях, где relaxed хватает. Насколько помню, volatile не является конпеляторным барьером и не запрещает переупорядочивать обычные записи вокруг себя. Только другие volatile и сайд-эффекты. Т.е. тот же мутекс из волатайла получится весьма хуёвый.
3.14159265 28.09.2020 15:20 # 0
Да.
> Разве что в примитивных случаях, где relaxed хватает.
Да. Именно тот пример, который я привёл. Когда переменная меняется один раз за всё время работы программы.
Даже пытаться использовать его как атомик — путь в ад.
guest8 28.09.2020 15:25 # −999
bormand 28.09.2020 15:26 # +1
guest8 28.09.2020 15:29 # −999
MAPTbIwKA 28.09.2020 15:21 # 0
>не является конпеляторным барьером
запись в волатилку всегда имеет сайд эффект, а значит код ПОСЛЕ него должен быть реально исполнен после этой записи, не?
bormand 28.09.2020 15:24 # 0
guest8 28.09.2020 15:27 # −999
bormand 28.09.2020 15:29 # 0
guest8 28.09.2020 15:32 # −999
3.14159265 28.09.2020 15:39 # 0
Любому вменяемому человеку понятно что использование volatile в качестве примитива синхронизации — полная хуйня.
Особенно в свете выхода С++11 и появления широкого набора кроссплатформенных альтернатив.
guest8 28.09.2020 15:45 # −999
bormand 28.09.2020 15:49 # 0
Спасибо что напомнил, я когда-то хотел попробовать, но у меня не было подходящего железа.
MAPTbIwKA 28.09.2020 15:52 # 0
bormand 28.09.2020 16:09 # +1
Myxa 28.09.2020 16:15 # 0
bormand 28.09.2020 16:16 # +1
Myxa 28.09.2020 16:22 # 0
DypHuu_niBEHb 29.09.2020 00:32 # +1
Вика пишет, что Dependent loads can be reordered, хотя мне это не очень понятно. Видимо бывают сорта депенденсов.
А что у ваших армов с кококококогерентностью кеша? попадание в кеш-то гарантировано бродкастится во все кеши, или тоже нужно явно?
3.14159265 28.09.2020 15:49 # 0
MAPTbIwKA 28.09.2020 15:50 # 0
В джаве вроде бы нет, потому volatile сделает fence.
guest8 28.09.2020 15:56 # −999
gost 28.09.2020 13:52 # +1
То получится вот это:
bormand 28.09.2020 13:54 # +2
gost 28.09.2020 13:57 # 0
3.14159265 28.09.2020 14:09 # 0
Рекомендую почитать JCP, там все эти вопросы очень подробно разобраны.
Desktop 28.09.2020 13:57 # +1
оказалось, что это тот же неймспейс
https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange
3.14159265 28.09.2020 13:57 # 0
Я уже вроде приводил контр-пример.
bormand 28.09.2020 14:01 # 0
То, что на интеле всё когерентно и в атомик риде нет барьеров и специальных инструкций, поэтому и обычное чтение сойдёт?
3.14159265 28.09.2020 14:01 # 0
>То, что на интеле всё когерентно
А на других платформах разве будут проблемы?
bormand 28.09.2020 14:02 # 0
3.14159265 28.09.2020 14:05 # 0
У нас есть признак шатдауна. Он может поменяться только одним способом (из 0 в 1).
Как только он стал ненулевым рано или поздно все это заметят и остановятся.
bormand 28.09.2020 14:13 # +1
Если у тебя там какая-то хуйня с тыщей ядер, то им будет очень дорого следить за кешами друг-друга. В лучшем случае они будут мониторить только реальные записи в память (т.е. тебе понадобится write-through семантика на done = 1). В худшем случае они вообще ничего не будут мониторить (и тогда нужно чтение с инвалидацией кеша на !done). volatile ничего из этого не даёт.
3.14159265 28.09.2020 14:23 # 0
Я к тому что атомик-чтения и особенно мьютексы были бы слишком дорогими в этой ситуации с флагом.
volatile раньше был вполне адекватным методом.
Но в целом после завоза в кресты std::memory_order и happens-before семантики оно бесполезно.
bormand 28.09.2020 14:33 # 0
Когда юзер психанёт и ткнёт в резет, ага. Представь, что это были ядерные треды, которые никто никогда не вытеснит.
> слишком дорогими
Да вот нихуя. Либо атомарное чтение бесплатное уже включено в цену (интел, арм) либо без него твой код тупо не работает (альфа?)
3.14159265 28.09.2020 14:49 # 0
Я вижу только одну ситуацию, что они не увидят друг-друга: в какой-то NUMA машине из нескольких сокетов. Но в таких обычно стараются делать NUMA-aware пулы чтобы потоки взаимодействовали в рамках своего сокета.
>атомарное чтение уже включено в цену (интел, арм)
Разве? Там же лишний LFENCE будет на каждом чтении, не?
bormand 28.09.2020 14:53 # 0
Зачем? Зачем? Посмотри на годболте во что атомарное чтение раскрывается.
На арме вроде тоже чтение бесплатно, а вот в записи барьер. Но я не изучал их модель.
gost 28.09.2020 14:55 # 0
Вон, я выше по ветке привёл реальный пример. В «mov eax, DWORD PTR p[rip]» раскрывается.
3.14159265 28.09.2020 15:00 # 0
bormand 28.09.2020 15:01 # +1
3.14159265 28.09.2020 15:09 # +1
Подтверждаю.
Так читаешь код, и даже не уверен в его атомарности.
guest8 28.09.2020 15:02 # −999
3.14159265 28.09.2020 15:09 # +1
В Йажа точно то же.
https://openjdk.java.net/jeps/171
guest8 28.09.2020 15:11 # −999
3.14159265 28.09.2020 15:13 # +1
3.14159265 28.09.2020 14:58 # 0
В ваших крестах с блядскими перегрузочками нихера не понятно. Какие именно гарантии у этого чтения?
bormand 28.09.2020 14:59 # 0
Пиши явно: while (!p.load(std::memory_order_relaxed)), тогда бесплатно. Для флажка об остановке релакса должно хватить.
3.14159265 28.09.2020 14:32 # 0
Так а они ведь как-то видят изменения volatile-переменных после прерываний.
bormand 28.09.2020 14:35 # 0
Прерывание происходит на том же самом ядре, со своим собственным кешем проблем не будет.
А для MMIO с железками как правило write back отключен, поэтому проц всё честно пишет.
Myxa 28.09.2020 15:12 # +1
VXP 09.10.2020 19:11 # 0
rotoeb 09.10.2020 19:14 # 0
Sers 09.10.2020 22:17 # 0
CHayT 09.10.2020 21:31 # 0
Sers 10.10.2020 17:52 # 0