LINUX.ORG.RU

pthreads and volatile chared data?


0

0

Утверждается, что шаренные данные не следует делать волатильными:

http://w3imagis.imag.fr/Membres/Eric.Ferley/pthreadsFAQ.html#Q56The

> Q56: Why don't I need to declare shared variables VOLATILE?

> system CANNOT require you to use volatile on shared variables for correct behavior, because POSIX requires only that the POSIX synchronization functions are necessary. ... if your program breaks because you didn't use volatile, that's a SYSTEM bug.

Кто-нибудь может прокомментировать?

★★★★★

Пример:

static volatile int sub_thread_started = 0;

int thread_func(void *arg)
{
        do_initialization();

        sub_thread_started = 1;
}

int main(void)
{
        pthread_create(... thread_func ...);

        while (!sub_thread_started)
                ;

        do_something_with_sub_thread();
}

это просто не будет работать без volatile.

я согласен, что этот код сомнительного качества. думаю,
что - как правило - использование volatile говорит о том,
что нужно использовать какие-то примитивы синхронизации,
но это:

> if your program breaks because you didn't use volatile, that's a
> SYSTEM bug.

перебор, я думаю.

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

2idle:

> ... нужно использовать какие-то примитивы синхронизации,

А как синхронизация поможет, если глобальная переменная была соптимизирована в регистр _до_ засыпания на примитиве синхронизации?

Или это предусмотрено?

Die-Hard ★★★★★
() автор топика

я, ламер пресноводный, попробую озвичить скромные мысли: всё ведь упирается в компилятор: volatile + busy wait - компилятор проявляет излишний интеллект при оптимизации, автор же по линку налегает на POSIX и рядом лежащие библиотеки: всё управление происходит через них и не по идее (тут самая сомнительная часть) компилятор не может "соптимизировать", т.к. используются одни и те же рычаги управления: если кто-то ждёт на семафоре, то он разблокируется тем же рычагом, коим воспользовались для поднятия семафора - нету среднего звена.

В общем я так понял мысль автора :)

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

2Pi:

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

Он засовывает переменную в регистр, программа шуршит, затем засыпает, затем получает слайс -- происходит переключение контакста и -- внимание! Что окажется в регистре?

В этом, собственно, и состоит вопрос.

А если тред не добрался до ядра вообще, а только пару раз крутанулся на спинлоке в юзерспейсе (вроде, все разумные примитивы синхронизации именно так поступают?) -- гарантирует ли Позикс, что регистры не из стека восстановятся, а обновятся? И если да, то как это возможно на уровне юзерспейсовского спинлока?

Конечно, проще всего посмотреть на код... Нету времени, надо знать ответ завтра утром, а еще кучу дел сделать.

Die-Hard ★★★★★
() автор топика
Ответ на: комментарий от Die-Hard

> ... нужно использовать какие-то примитивы синхронизации,
>
> А как синхронизация поможет, если глобальная переменная была соптимизирована
> в регистр _до_ засыпания на примитиве синхронизации?

прежде всего, использование примитива часто делает ненужным
переменные типа 'sub_thread_started'.

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

        int VAR;

        whatewer_lock_t LOCK;   // семафор, спинлок

        void ugly_but_correct_VAR_atomic_add(int value)
        {
                lock(&LOCK);

                VAR += value;
                                // "asm" pseudo output:
                                //
                                // load VAR -> register
                                // add register, value
                                // store register -> VAR

                unlock(&LOCK);
        }

это работает потому, что в числе прочего мы имеем следующие
гарантии:

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

        unlock() влечет барьер по записи.
        т.е, результат "store register -> VAR" будет виден
        -не позже- освобождения семафора.

по поводу второго: вполне возможно, что после возврата из
этой функции все изменения (запись в VAR и запись в LOCK
при освобождении) еще локальны, те болтаются в кэше и не
видны другим процессорам (я не говорю конкретно про i386,
здесь у нас модель памяти strong). однако, если другой
процессор пытается захватить этот семафор, и "видит", что
он свободен, он также обязан видеть и новое значение VAR,
те, все работает как надо.

другими словами, другой процессор не получит семафор до
тех пор, пока новое значение VAR не достигло main memory.

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

маленькое дополнение:

> другими словами, другой процессор не получит семафор до
> тех пор, пока новое значение VAR не достигло main memory.

... вместе с соответствующими cache-invalidate уведомлениями.

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

2idle:

Thanks alot!

> lock() влечет барьер по чтению.

А кто барьеры ставит?

Например, если у меня в спинлоке стоит rmb(), то вот он, А если не стоит? Значит ли это, что у меня спинлок кривой?

Die-Hard ★★★★★
() автор топика
Ответ на: комментарий от Die-Hard

> А кто барьеры ставит?
> Например, если у меня в спинлоке стоит rmb(), то вот он, А если не стоит?
> Значит ли это, что у меня спинлок кривой?

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

кроме того,

> Например, если у меня в спинлоке стоит rmb()

на самом деле, все сложнее. rmb для lock() _недостаточно_.
нужен еще и барьер для записи, те, все stores после взятия
семафора должны пройти -не раньше- записи в сам семафор
(означающей, что мы его захватили). на практике, я думаю,
lock() всегда является полным барьером: wmb. однако, насколько
я понимаю, следующий пример в теории возможен, и коррeктен:

        var1 = VAR1;    // read global
        VAR2 = var2;    // write global

        lock();
        ...
        unlock();

        var3 = VAR3;
        VAR4 = var4;

        // обе операции с var1/var2
        // на шину выходят _после_
        // unlock().

        // обе операции с var3/var4
        // происходят _перед_ lock()

по крайней мере, выполнение 'var3 = VAR3;' _до_ unlock()
совершенно точно возможно на практике. остальное - опять
таки, насколько мне известно - только в теории.

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