LINUX.ORG.RU

Когерентность cpu

 ,


3

8

Добрый день. Я напишу по памяти, возможно с ошибками:

#include <iostream>
#include <thread>
int *ptr;
void fn()
{
    // Воткнуть ли aquire барьер?
    std::cout << *ptr;
}
int main()
{
    ptr = new int{0};
    // Воткнуть ли release барьер?
    std::thread t(fn);
    t.join();
    return 0;
}
Насколько знаю, пара барьеров вшита в thread::join() и при завершении потока. Имеются ли невидимые барьеры в данном случаи?

★★

пара барьеров вшита в thread::join() и при завершении потока

t.join();

Имеются ли невидимые барьеры

Даже не знаю.

Kiborg ★★★
()

А все подписались за лулзами?

Прост я всегда думал что барьер содержится в любом вызове с переключением контекста.

И не знаю ни одной платформы, кроме отъявленного самопала, где старт потока не был бы системной функцией, работающей с ядрёным обьектом.

Вот что там в стандарте, хрен его знает, сходу чтот не вычитал.

pon4ik ★★★★★
()

А с чем автор собрался согласовывать CPU и причем тут кусок кода на С++?

Велик и могуч русский язык, но, блин, без латыни сказать ничего не умеем.

AF ★★★
()
Ответ на: комментарий от AF

А с чем автор собрался согласовывать CPU и причем тут кусок кода на С++?

Два потока могут параллельно исполняться на двух процессорах, которые могут видеть один и тот же участок памяти по разному (если один отложит передачу данных во вне).

Велик и могуч русский язык, но, блин, без латыни сказать ничего не умеем.

Тут в принципе всё понятно, если знать что вопрос о модели памяти и гарантиях, которые она (не) предоставляет.

Вопрос в топике другими словами: гарантировано ли, что ptr будет присвоено значение в момент его использования или для этого нужно ещё вызовы специальных функций добавлять.

xaizek ★★★★★
()

Любой вызов функции, в которой компилятор не знает, что происходит является полным барьером (потому что там может быть полный барьер). Системные вызовы тоже являются полным барьером (возможно, есть исключения), а std::thread t(fn) явно будет сделано с помощью clone.

Однако, атомарные переменные на то и придумали, чтобы синхронизировать потоки. Поэтому, тут полезно сделать ptr атомарным указателем, запись в него делать с флагом release, а чтение memory_order_consume (на ARM, который out of order, может оказаться быстрее, но до тех пор, пока ты синхронизируешь только данные, доступные через ptr) или memory_order_acqire, потому что в дальнейшем любое изменение в твоем коде может превратить синхронизацию потоков в тыкву. IMHO, если нужна синхронизация доступа к общим данным, то нужно использовать атомики. Во-первых, это будет работать, пока стандарт не поменяют, во-вторых, если ты используешь атомик, то для читателя кода будет понятно, что есть разделение данных (это не нужно будет выводить из неявного способа синхронизации). Ну, или использовать mutex.

anonymous
()
Ответ на: комментарий от xaizek

Если вопрос про модели памяти, то причем тут пример кода?
с++ подразумевает, что если что в память записано что-то одним потоком, то второй таки может прочитать. (С единственной оговоркой по volatile).
Если же железо работает с памятью как-то по другому, то єто не проблемы языка.

AF ★★★
()
Ответ на: комментарий от AF

Если вопрос про модели памяти, то причем тут пример кода?

К тому, что стандарт языка определяет эту модель, а стандарт этот код и описывает.

с++ подразумевает, что если что в память записано что-то одним потоком, то второй таки может прочитать. (С единственной оговоркой по volatile).

Прочитать может, а вот будет там старое или новое значение другой вопрос. И да, модель памяти в стандарте начиная с C++11.

Если же железо работает с памятью как-то по другому, то єто не проблемы языка.

Язык он как бы не в вакууме, программы на этом языке работают на железе, поэтому стандарт описывает как конструкции языка должны себя вести на таком железе.

xaizek ★★★★★
()
Последнее исправление: xaizek (всего исправлений: 2)
Ответ на: комментарий от AF

Нет. Запись в переменную не гарантирует сиюминутную запись в RAM, а чтение значенмя переменной - чтение значения из ОЗУ.

Рекомендую почитать Энтони Уильямса. У него очень хорошо про это написано с примерами на новых крестах.

trex6 ★★★★★
()
Ответ на: комментарий от AF

Ваше утверждение неверно.

Есть cpu reordering и compiler reordering и с тех пор как с++ узнал о потоках он узнал и о enum memory_order { memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_release, memory_order_acq_rel, memory_order_seq_cst };

и прочих радостях.

anonymous
()
Ответ на: комментарий от anonymous

Любой вызов функции, в которой компилятор не знает, что происходит является полным барьером (потому что там может быть полный барьер).

Это логическая выкладка, или есть ссылка на стандарт?

Просто я не нашёл, утверждения в стандарте (смотрел драфт 11ого), которое вещало бы об установке полного барьера в конструкторе thread, или обязывании реализации проследить за этим.

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

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

30.3.1.2 Synchronization: The completion of the invocation of the constructor synchronizes with the beginning of the invocation of the copy of f

Так что спи спокойно.

anonymous
()
Ответ на: комментарий от pon4ik

Ах да, забыл добавить. Копирование аргументов происходит в вызывающим конструктор потоке.

Так что на момент начала работы функции нового потока гарантируется, что в текущем все выполнено. Никаких доп. Барьеров лепить не надо.

anonymous
()
Ответ на: комментарий от anonymous

О, благодарствую, теперь мой сон будет крепок аки у младенца.

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

Любой вызов функции, в которой компилятор не знает, что происходит является полным барьером (потому что там может быть полный барьер).

Это логическая выкладка, или есть ссылка на стандарт?

Логическое соображение. Кажется, я это отсюда взял: Herb Sutter - atomic<> Weapons.

которое вещало бы об установке полного барьера в конструкторе thread,

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

И, кстати, выше ответил другой анонимус. :)

anonymous
()
Ответ на: комментарий от xaizek

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

AF ★★★
()
Ответ на: комментарий от anonymous

Ну ктож вас разберёт :)

Не очень логично получается про любые неизвестные функи и сисколы. Кому кому а компилятору глубоко фиолетово есть ли в функе барьер.

Более того, если компиль начнёт ставить барьеры перед каждой функой, содержимое которой ему неизвестно, то его за это надо больно бить.

Т.е. суть не понятно в чём заключается тут смысл слова «считается». Считается кем? Если компилятором, то непонятно что он предпримет посчитав такое, не поставит барьер?. Если человеком, то как быть, если в функе таки нет барьера, а ты весь такой посчитал что он есть?

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

Всё смешалось: кони, люди...

В терминологии не силен, кажется, это называется «видимый(наблюдаемый) порядок выполнения». Если утрировать, то на уровне си два креста есть такое правило: если видимый порядок выполнения не нарушается, то компилятор и\или процессор могут выполнять код как им вздумается.

Пример

a = 1;
b = 2;
f();
c = 3;


Компилятор\процессор могут присвоить b значение раньше, чем a, тк это не нарушает видимый порядок выполнения, но они не имеют права присвоить значение c = 3, до вызова внешней функции f(), потому что неизвестно, что она делает. Но к потокам это имеет посредственное отношение.

Эту ночь тоже можно спать спокойно...
anonymous
()
Ответ на: комментарий от anonymous

Одна маленькая поправочка.
Если компилятор заинлайнит f(); и в этой функции не используется значение с, то переставить таки может.

AF ★★★
()
Ответ на: комментарий от anonymous

Ты путаешь точки следования и барьеры не?

Барьеры похожи на точки следования, только точки следования про запись в абстрактную память (кэш или оперативу, куда повезёт, остаётся на откуп системе).

А барьеры это многопоточное уточнение что таки в память а не в кэш.

Например ты знаешь, что на поддерживаемых платформах, выровненный указатель пишется атомарно. При использовании барьера, можешь писать такие указатели вообще без блокировок. Так будет типо быстрее, ибо блокировка помимо барьера содержит ещё всякой хрени.

Ну по крайней мере это та нелепая модель что в моей черепной коробке проживает.

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

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

anonymous
()
Ответ на: комментарий от anonymous

Ок, значит я попутал :) Сложно трещать с двумя анонами.

pon4ik ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.