Когерентность данных
От: Aniskin  
Дата: 23.07.23 13:07
Оценка:
Исходная ситуация: значение переменной Int не определено, значение переменной Bool = false
Поток1 пишет новое значение в переменную Int, а затем в переменную Bool значение true.
Поток2 читает переменную Bool, и если она True, то читает переменную Int.

Вопрос: может ли Поток2 быть уверен в том, что если переменная Bool == true, то переменная Int гарантированно содержит то значение, которое записал Поток1. Или на многопроцессорных системах это не гарантированно?
Re: Когерентность данных
От: Qulac Россия  
Дата: 23.07.23 13:15
Оценка:
Здравствуйте, Aniskin, Вы писали:

A>Исходная ситуация: значение переменной Int не определено, значение переменной Bool = false

A>Поток1 пишет новое значение в переменную Int, а затем в переменную Bool значение true.
A>Поток2 читает переменную Bool, и если она True, то читает переменную Int.

A>Вопрос: может ли Поток2 быть уверен в том, что если переменная Bool == true, то переменная Int гарантированно содержит то значение, которое записал Поток1. Или на многопроцессорных системах это не гарантированно?


Если это был весь сценарий то, да. А для общих случаев — нет.
Программа – это мысли спрессованные в код
Re: Когерентность данных
От: Pzz Россия https://github.com/alexpevzner
Дата: 23.07.23 13:22
Оценка: 10 (1) +4
Здравствуйте, Aniskin, Вы писали:

A>Вопрос: может ли Поток2 быть уверен в том, что если переменная Bool == true, то переменная Int гарантированно содержит то значение, которое записал Поток1. Или на многопроцессорных системах это не гарантированно?


Ключевое слово: memory barrier.
Re[2]: Когерентность данных
От: Aniskin  
Дата: 23.07.23 13:33
Оценка:
Здравствуйте, Qulac, Вы писали:

Q>Если это был весь сценарий то, да. А для общих случаев — нет.


Спасибо. А можно пару слов об общих случаях?
Re[3]: Когерентность данных
От: Qulac Россия  
Дата: 23.07.23 13:40
Оценка:
Здравствуйте, Aniskin, Вы писали:

A>Здравствуйте, Qulac, Вы писали:


Q>>Если это был весь сценарий то, да. А для общих случаев — нет.


A>Спасибо. А можно пару слов об общих случаях?


Если есть третий пишущий поток, пока поток2 шел от bool к int он мог int уже переписать и там будет уже не то значение, что записал поток1
Программа – это мысли спрессованные в код
Re: Когерентность данных
От: Буравчик Россия  
Дата: 23.07.23 13:41
Оценка: +1
Здравствуйте, Aniskin, Вы писали:

A>Вопрос: может ли Поток2 быть уверен в том, что если переменная Bool == true, то переменная Int гарантированно содержит то значение, которое записал Поток1. Или на многопроцессорных системах это не гарантированно?


В общем случае зависит от предоставленных гарантий — модели памяти (happens before)
Т.е. конкретный ответ зависит от того, что ты называешь потоком.

Например, компилятор запросто может поменять два действия, выполненные в рамках одного потока (установка int и bool), т.к. они не зависят друг от друга.
Best regards, Буравчик
Re[2]: Когерентность данных
От: Aniskin  
Дата: 23.07.23 13:50
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>В общем случае зависит от предоставленных гарантий — модели памяти (happens before)

Б>Т.е. конкретный ответ зависит от того, что ты называешь потоком.

Потоком я называю поток исполнения, создаваемый CreateThread. Или я не правильно понял комментарий?

Б>Например, компилятор запросто может поменять два действия, выполненные в рамках одного потока (установка int и bool), т.к. они не зависят друг от друга.


Будем считать, что компилятор в моем случае не меняет местами установку int и bool. Мой вопрос можно свести к вопросу, а может ли bool записаться в память раньше чем int с точки зрения потока2.
Re[3]: Когерентность данных
От: Pzz Россия https://github.com/alexpevzner
Дата: 23.07.23 13:58
Оценка:
Здравствуйте, Aniskin, Вы писали:

A>Будем считать, что компилятор в моем случае не меняет местами установку int и bool. Мой вопрос можно свести к вопросу, а может ли bool записаться в память раньше чем int с точки зрения потока2.


Может.
Re[4]: Когерентность данных
От: Aniskin  
Дата: 23.07.23 14:05
Оценка:
Здравствуйте, Pzz, Вы писали:

A>>Будем считать, что компилятор в моем случае не меняет местами установку int и bool. Мой вопрос можно свести к вопросу, а может ли bool записаться в память раньше чем int с точки зрения потока2.


Pzz>Может.


Понятно. Правильно ли я понимаю, что использование любого примитива синхронизации заставит CPU сбросить все кеши, и устранит неоднозначность моей ситуации?
Re[5]: Когерентность данных
От: Pzz Россия https://github.com/alexpevzner
Дата: 23.07.23 14:21
Оценка: 4 (1) +1
Здравствуйте, Aniskin, Вы писали:

Pzz>>Может.


A>Понятно. Правильно ли я понимаю, что использование любого примитива синхронизации заставит CPU сбросить все кеши, и устранит неоднозначность моей ситуации?


У тебя есть две переменные, и ты хочешь, чтобы работа с ними из двух потоков была консистентной. Проще всего, действительно, использовать примитивы синхронизации:

    mutex m;
    int   i;
    bool  b;

    void set_i (int val) {
        lock(m);
        i = val;
        b = true;
        unlock(m);
    }

    int get_i (void) {
        int val;

        lock(m);
        if (b) {
            val = i;
        } else {
            val = 5; // Why not?
        }
        unlock(m);

        return val;
    }


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

Возможно, тебе хватили бы atimic-ов. Но atomic-и сложны для понимания, а мьютексы просты. Кроме того, атомики тоже отнюдь не бесплатны.

В общем, лучше отталкиваться от проблемы, которую ты пытаешься решить, а не от имеющегося низкоуровнего API. Если, конечно, твоя проблема не заключается в знакомстве с этим API, как таковым.

P.S. Кеши CPU тут вообще не при чем. На x86 синхронизация кешей происходит автоматически. Даже и между разными ядрами одноко процессора. И даже если в материнскую плату вставлено несколько процессоров (такое бывает в природе, но редко встречается на дектопе), между ними сихнронизация кешей тоже происходит автоматически.

Но процессор вправе исполнять инструкции не в том порядке, в каком они написаны в программе, а как ему удобнее — при условии, что суть не меняется. И это реально случается, и дело не в кешах, а в том, что обращение к памяти — это долгая асинхронная операция. И пока она идет, процессор вправе заняться чем-нибудь еще.

При этом в понятие "суть не меняется" не входят гарантии, что глядя с другого ядра на порядок операций с памятью, мы увидим такой же порядок, в каком они были запущены на текущем ядре. Чтобы на это повлиять, существуют барьеры памяти, они есть сами по себе и в том или ином виде встроены в атомики и в примитивы синхронизации.
Re[6]: Когерентность данных
От: Aniskin  
Дата: 23.07.23 14:48
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>В общем, лучше отталкиваться от проблемы, которую ты пытаешься решить


Есть список элементов, с каждым элементом ассоциированы данные. Данных изначально нет, получение данных является тяжелой операций, но выполняется она один раз отдельно для каждого элемента. Поэтому данные я получаю только при обращении к ним. Обращение к элементам и данным происходит из разных потоков. У каждого элемента есть bool-поле HasData, определяющие то, получены данные для элемента, или нет. Изначально я хотел сделать так:

1) читатель проверяет HasData,
2) и если равно true, то читатель просто забирает данные, (что бы не входить в критическую секцию)
3) а если равно false, то читатель заходит в критическую секцию, снова проверяет HasData, при необходимости получает данные и устанавливает HasData в true, после чего забирает данные и выходит из секции.

Но у меня возникли сомнения относительно корректности пунктов 1-2. Поскольку вы пояснили мне, что так делать нельзя, то я убираю проверку HasData вне секции, и потоки всегда будут начинать с критической секции.
Re[7]: Когерентность данных
От: Pzz Россия https://github.com/alexpevzner
Дата: 23.07.23 15:03
Оценка:
Здравствуйте, Aniskin, Вы писали:

A>1) читатель проверяет HasData,

A>2) и если равно true, то читатель просто забирает данные, (что бы не входить в критическую секцию)
A>3) а если равно false, то читатель заходит в критическую секцию, снова проверяет HasData, при необходимости получает данные и устанавливает HasData в true, после чего забирает данные и выходит из секции.

Я бы сделал HasData atomic-ом, и спал спокойно.

A>Но у меня возникли сомнения относительно корректности пунктов 1-2. Поскольку вы пояснили мне, что так делать нельзя, то я убираю проверку HasData вне секции, и потоки всегда будут начинать с критической секции.


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

Еще один момент, если обращений на чтение много а на запись — не очень, выглядит так, что rwlock может быть уместнее простого мьютекса.
Re[8]: Когерентность данных
От: Aniskin  
Дата: 23.07.23 15:14
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>Я бы сделал HasData atomic-ом, и спал спокойно.


Т.е. если поток1 устанавливает Int, а затем делает InterlockedIncrement(HasData), то в потоке2 я могу проверять HasData != 0 и считать, что Int гарантированно корректен?
Re: Когерентность данных
От: kov_serg Россия  
Дата: 23.07.23 16:32
Оценка:
Здравствуйте, Aniskin, Вы писали:

A>Исходная ситуация: значение переменной Int не определено, значение переменной Bool = false

A>Поток1 пишет новое значение в переменную Int, а затем в переменную Bool значение true.
A>Поток2 читает переменную Bool, и если она True, то читает переменную Int.

A>Вопрос: может ли Поток2 быть уверен в том, что если переменная Bool == true, то переменная Int гарантированно содержит то значение, которое записал Поток1. Или на многопроцессорных системах это не гарантированно?

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

Store buffer: https://youtu.be/nh9Af9z7cgE?t=299
Re[9]: Когерентность данных
От: Pzz Россия https://github.com/alexpevzner
Дата: 23.07.23 16:51
Оценка:
Здравствуйте, Aniskin, Вы писали:

A>Здравствуйте, Pzz, Вы писали:


Pzz>>Я бы сделал HasData atomic-ом, и спал спокойно.


A>Т.е. если поток1 устанавливает Int, а затем делает InterlockedIncrement(HasData), то в потоке2 я могу проверять HasData != 0 и считать, что Int гарантированно корректен?


Ну как-то мне кажется так, примерно:

    if (InterlockedAdd(&HasData, 0) {
        return data;
    }

    MutexLock();

    if (!HasData) {
        LoadData();
        InterlockedAdd(&HasData, 1); // HasData = 1, atomically
    }

    MutexUnlock();

    return data;
Re[2]: Когерентность данных
От: Pzz Россия https://github.com/alexpevzner
Дата: 23.07.23 16:55
Оценка:
Здравствуйте, kov_serg, Вы писали:

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


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

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

При этом любое ядро само с собой self-consistent. Но не обязательно консистентно с другими ядрами. Впрочем, в user space поток может в любое время сам собой переехать на другое ядро, так что для user-space кода это не очень важное замечание.
Re[3]: Когерентность данных
От: kov_serg Россия  
Дата: 23.07.23 17:25
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>Тут вот такой нюанс, невредный для понимания: на x86 кеши синхронизируются автоматически. Даже когда не то, что ядер, физических процессоров больше одной штуки.

x86 выполняют команды не по порядку и данные при записи не сразу попадают в кэш, там есть промежуточный буфер.
Re[10]: Когерентность данных
От: Aniskin  
Дата: 23.07.23 18:06
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>Ну как-то мне кажется так, примерно:


Pzz>
Pzz>    if (InterlockedAdd(&HasData, 0) {
Pzz>        return data;
Pzz>    }

Pzz>    MutexLock();

Pzz>    if (!HasData) {
Pzz>        LoadData();
Pzz>        InterlockedAdd(&HasData, 1); // HasData = 1, atomically
Pzz>    }

Pzz>    MutexUnlock();

Pzz>    return data;
Pzz>


Вопрос тот же. Этот код обеспечивает когерентность HasData, но обеспечивает ли он когерентность data?
Re[11]: Когерентность данных
От: Pzz Россия https://github.com/alexpevzner
Дата: 23.07.23 18:14
Оценка: 2 (1)
Здравствуйте, Aniskin, Вы писали:

A>Вопрос тот же. Этот код обеспечивает когерентность HasData, но обеспечивает ли он когерентность data?


Да, за счет InterlockedXXX, которые подразумевают внутри себя барьеры памяти и мьютекса, который гарантирует, что данные и HasData изменяются консистентно.
Re[12]: Когерентность данных
От: Aniskin  
Дата: 23.07.23 18:53
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>мьютекса, который гарантирует


Если я правильно понимаю, то я могу использовать как мьютекс, так и критическую секцию (суть тот же мьютекс)?
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.