Но ведь здесь не используются примитивы синхронизации! С моей точки зрения, это как раз опасная практика -- изменять значение в одном потоке без блокирования чтения из другого потока.
Если бы вы использовали синхронизацию доступа к go, то volatile, наверняка, не потребовался бы.
W>Достаточно часто реально используется следующая схема. W>Есть рабочий поток, который в цикле выполняет интенсивные вычисления. Рабочий поток периодически опрашивает состояние глобальной переменной и, если всё ок, продолжает работать, иначе останавливается. Если пользователь решил отменить действие, GUI-поток меняет значение этой переменной. W>
W>#include <iostream>
W>#include <process.h>
W>#include <windows.h>
W>/*volatile*/bool go = true;
W>const unsigned int MaxIter = 1000000000;
W>// Эта процедура выполняет интенсивный вычисления.
W>unsigned int __stdcall Calc(void *)
W>{
W> double sum = 0.0;
W> for (unsigned int i = MaxIter; (i != 0) && go; --i)
W> sum += 1. / i;
W> std::cout << "sum = " << sum << std::endl;
W> return 0;
W>};
W>int main()
W>{
W> unsigned int id;
W> HANDLE h = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0, &Calc, NULL, 0, &id));
W> // Пользователь подумал 20 миллисекунд
W> ::Sleep(20);
W> // .. и решил отменить вычисления
W> go = false;
W> ::WaitForSingleObject(h, INFINITE);
W> ::CloseHandle(h);
W> return 0;
W>}
W>
W>Так вот, когда volatile закомментирован, то рабочий поток не останавливается программа выводит: W>
W>21.3005
W>А когда volatile есть, поток останавливается и программа выводит: W>
> > Знавчицца так: Берёшь простенькую программу с обьявленной глобальной volatile и нет переменной. Компилируешь в релизе разными компиляторами. Я проверял на: VC6, VC7.1, gcc 2.95, gcc 3.2, Sun One Studio 5. И смотришь разницу
Разницу в чем?
Еще раз, почитай по указанным ссылкам.
> Ещё раз говорю, что переменная, использующаяся в разных потоках, кроме того, что должна быть защищена приметивами синзхронизации обязана быть volatile.
Ok, тогда запость, пожалуйста, Александру Терехову ответ на его сообщение http://groups-beta.google.com/group/comp.std.c/msg/ae49dc7a96c625f5 в ньюсгруппу и копию на его e-mail, что ерунду он написал что volatile не нужен для синхронизации в multithreading, а мы тут все вместе почитаем его ответ.
Здравствуйте, Шахтер, Вы писали:
Ш>Они уже приведены.
То, что я видел выше нельзя назвать примерами реальных проектов. Это небольшие тестовые программки, которые дополнены комментариями типа "вот когда-нибудь, когда придут крутые-крутые компиляторы, которые увидят, как работают WaitForSingleObject/pthread_mutex_wait, вот тогда...". И еще говорят, что компилятор справедливо опасается оптимизировать доступ к переменным, если перед этим были сделалы какие-то внешние вызовы.
Как мне кажется, довольно характерный пример псевдокода по работе о общими данными в многопоточных приложениях выглядит так:
// Кто-то, что-то готовит.
lock();
...
flag = ready;
unlock();
// Кто-то, что-то ожидает.while( true )
lock();
if( ready != flag )
unlock();
else
...
ready = not_ready;
unlock();
Причем как раз доступ к общим данным (в данном случае, flag) производиться рядышком с использованием синхронизирующих примитивов.
Из того, что я прочитал, я сделал вывод, что компилятор не будет оптимизировать доступ к общим данным, если рядом есть вызов синхронизирующего примитива (просто потому, что компилятор будет считать этот вызов вызовом с побочными эфектами). Компиляторы не делают такой оптимизации сейчас. И я сомневаюсь, что они будут делать ее в будущем. Хотя бы из-за соображений совместимости.
Поэтому пока я не считаю нужным делать рефакторинг своего кода и вставлять volatile туда, где его не было. И я не увидел пока реальных примеров из жизни, которые заставили бы меня это сделать. А было бы полезно увидеть (не только мне), если такие примеры имели место быть.
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
ME>Разницу в чем?
Разницу в генерируемом компилятором коде.
>> Ещё раз говорю, что переменная, использующаяся в разных потоках, кроме того, что должна быть защищена приметивами синзхронизации обязана быть volatile.
ME>Ok, тогда запость, пожалуйста, Александру Терехову ответ на его сообщение http://groups-beta.google.com/group/comp.std.c/msg/ae49dc7a96c625f5 в ньюсгруппу и копию на его e-mail, что ерунду он написал что volatile не нужен для синхронизации в multithreading, а мы тут все вместе почитаем его ответ.
Ты путаешь понятия (или подменяешь). Мы говорим не о том, что volatile необходим только в multithreaded приложениях, а о том что он необходим при использовании переменной несколькими потоками (процессами), но это не отменяет его применимости в других ситуациях.
Здравствуйте, MaximE, Вы писали:
>> ME>Мой поинт был в том (если этого до сих пор непонятно уважаемому Шахтеру), что какой бы семантикой не обладал volatile, ее просто не достаточно для multithreading, и поэтому volatile бесполезен для multithreading. >> >> не вдаваясь в технические подробности, из "недостаточно" никак не может следовать "и поэтому ... бесполезен"
ME>На этом основании ты хочешь объявить все написанное здесь ересью и провозгласить, что volatile необходим и достаточен для обеспечиния синхронизации при multithreading?
volatile — необходимое (в ряде случаев), но не достаточное условие
"For every complex problem, there is a solution that is simple, neat,
and wrong."
Мы говорим не о том, что volatile необходим только в multithreaded приложениях, а о том что он необходим при использовании переменной несколькими потоками (процессами)...
Это здесь уже обсудили. Он ни достаточен, ни необходим. Вся информация тебе доступна, читай.
AndrewJD wrote:
> Здравствуйте, MaximE, Вы писали: > >>> ME>Мой поинт был в том (если этого до сих пор непонятно уважаемому Шахтеру), что какой бы семантикой не обладал volatile, ее просто не достаточно для multithreading, и поэтому volatile бесполезен для multithreading. >>> >>> не вдаваясь в технические подробности, из "недостаточно" никак не может следовать "и поэтому ... бесполезен" > > ME>На этом основании ты хочешь объявить все написанное здесь ересью и провозгласить, что volatile необходим и достаточен для обеспечиния синхронизации при multithreading? > > volatile — необходимое (в ряде случаев), но не достаточное условие
Memory barriers must be applied where necessary on many architectures, and
there is no standard or portable way to generate them. There is no excuse for a
compiler to require both volatile AND memory barriers, because there's no
excuse for a compiler to reorder memory access around its own memory barrier
construct. (Usually either a compiler builtin such as Compaq C/C++ "__MB()" or
an asm("mb") "pseudocall".) The standard and portable way to ensure memory
consistency is to rely on the POSIX memory model, which is based solely on
specific POSIX API calls rather than expensive and inappropriately defined
language keywords or nonportable hardware instructions. A system or compiler
that does not provide the proper memory model (without volatile) with proper
use of the portable POSIX API calls does not conform to POSIX, and cannot be
considered for serious threading work. Volatile is irrelevant.
Entirely aside from the language issues, my point was simply that "volatile",
and especially its association with threaded programming, has been an extremely
confusing issue for many people. Simply using them together is going to cause
even more confusion. The illusory promise of volatile will lead novices into
trouble.
In contradiction to your absurd statement that "writing multithread programs
becomes impossible" without volatile, the intended C and C++ semantics
associated with volatile are neither useful nor sufficient for threaded code.
And it is WITH volatile, not without, that "the compiler wastes vast
optimization opportunities", especially as the expense of meeting the volatile
"contract" is of no benefit to threaded code.
ME>Это здесь уже обсудили. Он ни достаточен, ни необходим. Вся информация тебе доступна, читай.
То, что он нидостаточен — это естественно, Но он необходим. Советую не ляпать на клавиатуре, а попробовать самому
Tom wrote:
> ME>Это здесь уже обсудили. Он ни достаточен, ни необходим. Вся информация тебе доступна, читай. > То, что он нидостаточен — это естественно, Но он необходим.
Здравствуйте, eao197, Вы писали:
E>Здравствуйте, What, Вы писали:
E>Но ведь здесь не используются примитивы синхронизации! С моей точки зрения, это как раз опасная практика -- изменять значение в одном потоке без блокирования чтения из другого потока.
А зачем здесь синхронизация? Один поток пишет значение переменной, другой читает. Главное, чтобы значение было записано атомарно. Это в данном случае для x86 гарантируется.
С другой стороны, а если бы рабочих потоков было, скажем, 4 вместо 1. И все читали бы одну и ту же переменную. Тогда использование синхронизации могло бы привести к ненужным потерям производительности.
E>Если бы вы использовали синхронизацию доступа к go, то volatile, наверняка, не потребовался бы.
Наверняка, да, не потребовался бы.
Здравствуйте, MaximE, Вы писали:
ME>Еще раз — volatile абсолютно бесполезен для multithreading.
На мой взгляд, это слишком категорично. Volatile может быть полезен для multithreading.
Вы приводили ссылки:
You do NOT need volatile for threaded programming. You do need it when you share
data between "main code" and signal handlers, or when sharing hardware registers
with a device. In certain restricted situations, it MIGHT help when sharing
unsynchronized data between threads (but don't count on it -- the semantics of
"volatile" are too fuzzy).
Здравствуйте, What, Вы писали:
W>А зачем здесь синхронизация? Один поток пишет значение переменной, другой читает. Главное, чтобы значение было записано атомарно. Это в данном случае для x86 гарантируется.
Боюсь, что именно из-за таких предположений весь сыр бор вокруг volatile и начался. Я считаю, что есть принципиальные вещи: выделеную память нужно освобождать, открытые файлы закрывать, после входа в критическую секцию нужно выйти оттуда, ..., когда одна нить читает то, что другая может переписать, то это нужно синхронизировать. Это вопрос принципа (с моей точки зрения). И если ему следовать, то:
— мы не зависим от volatile или не volatile;
— мы не зависим от аппаратной платформы (могут быть какие-нибудь специализированные процессоры, на которых извлечение одного байта из памяти может не быть атомарной операцией, а требовать дополнительных арифметических операций/сдвигов);
— мы не зависим от типа данных. Сейчас у нас переменная go -- это bool. Завтра -- это long int, в котором указано, сколько итераций нам еще разрешают сделать. После завтра -- структура, в которой описывается, при каких условиях нужно выходить, а при каких условиях еще можно провести пару-тройку итераций.
А все из-за того, что изначально мы защитились синхронизацией. И не надеялись на особенности конкретной платформы и конткретного компилятора.
W>С другой стороны, а если бы рабочих потоков было, скажем, 4 вместо 1. И все читали бы одну и ту же переменную. Тогда использование синхронизации могло бы привести к ненужным потерям производительности.
За безопасность в многопоточности нужно платить. Некоторые из-за безопасности на более медленные языки программирования переходят Из-за того, что лишний раз delete вызвать лень
E>>Если бы вы использовали синхронизацию доступа к go, то volatile, наверняка, не потребовался бы. W>Наверняка, да, не потребовался бы.
От тож!
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
// VolatileTest.cpp : Defines the entry point for the console application.
//#include"stdafx.h"// Допустим флаг некоего потока (хотя конечно так лучше не делать)
//int Variable1 = 0;
//
// Некоя переменная для вычислений. Допустим она используеться
// одновременно из нескольких потоков.
//int Variable2 = 0;
int Thread()
{
while(Variable1 == 0)
{
Variable2 += 123;
if (++Variable2 > 6134)
{
Variable2 -= 534;
Variable2 *= Variable2;
}
else
{
Variable2 -= 32;
Variable2 += Variable2 / 2;
}
Sleep(1);
}
return 0;
}
void FireFlag()
{
Variable1 = 1;
}
int _tmain(int argc, _TCHAR* argv[])
{
//
// Need to look generated assembler code in the release version
//__asm int 3;
//
// Assume another thread here
//
Thread();
//
// Signal the Flag
//
FireFlag();
return 0;
}
Без волатайла
int Thread()
{
while(Variable1 == 0)
00401000 mov eax,dword ptr [Variable1 (4072C0h)]
00401005 test eax,eax
00401007 jne Thread+53h (401053h)
00401009 push esi
0040100A mov esi,dword ptr [__imp__Sleep@4 (405000h)]
{
Variable2 += 123;
if (++Variable2 > 6134)
00401010 mov ecx,dword ptr [Variable2 (4072C4h)] Один раз закешировали и начали использовать уже регистр
00401016 add ecx,7Ch
00401019 cmp ecx,17F6h
0040101F jle Thread+33h (401033h)
{
Variable2 -= 534;
00401021 sub ecx,216h
Variable2 *= Variable2;
00401027 mov eax,ecx
00401029 imul eax,ecx
0040102C mov dword ptr [Variable2 (4072C4h)],eax Слили в память только тут
}
else
00401031 jmp Thread+45h (401045h)
{
Variable2 -= 32;
00401033 sub ecx,20h
Variable2 += Variable2 / 2;
00401036 mov eax,ecx
00401038 cdq
00401039 sub eax,edx
0040103B sar eax,1
0040103D add ecx,eax
0040103F mov dword ptr [Variable2 (4072C4h)],ecx Слили в память только тут
}
Sleep(1);
00401045 push 1
00401047 call esi
00401049 mov eax,dword ptr [Variable1 (4072C0h)]
0040104E test eax,eax
00401050 je Thread+10h (401010h)
00401052 pop esi
}
return 0;
Вот и представь, что Thread, испольняется несколькими потоками... Выходит, что потоки узнают о изменениях переменной, только тогда, когда компилятор соизволит слить её в память, а он может этого неделать оочень долго... Почувствуйте разницу
Tom wrote:
> ME>Приведи конкретный пример. > > Сырец
[]
> Вот и представь, что Thread, испольняется несколькими потоками... Выходит, что потоки узнают о изменениях переменной, только тогда, когда компилятор соизволит слить её в память, а он может этого неделать оочень долго... Почувствуйте разницу
Этот код — некорректный многопоточный код, так как он не применяет синхронизацию для доступа к переменной.
Vet>В объекте класса есть переменная, которая используется двумя потоками.
Vet>Один из потоков иногда меняет ее значение.
Vet>Есть ли необходимость в этом случае делать переменную как volatile.
Есть, например простой пример:
volatile int var1 = 1; //ее используют два потока
...
int var2 = var1 = 2;
var2 = var1;
Видно, что в результате var2 = 2, но это если не было переключения между потоками ровно после первого присваивания. А если было, то тогда var2 будет иметь новое значение var1. А если не использовать volatile, то второе присваивание будет удалено в результате оптимизации.
Пример примитивный конечно, в реальной жизни все может быть намного сложнее, и особенно поиск ошибки такого типа.
Здравствуйте, What, Вы писали:
W>Здравствуйте, MaximE, Вы писали:
ME>>Еще раз — volatile абсолютно бесполезен для multithreading. W>На мой взгляд, это слишком категорично. Volatile может быть полезен для multithreading.
W>Вы приводили ссылки: W>You do NOT need volatile for threaded programming. You do need it when you share W>data between "main code" and signal handlers, or when sharing hardware registers W>with a device. In certain restricted situations, it MIGHT help when sharing W>unsynchronized data between threads (but don't count on it -- the semantics of W>"volatile" are too fuzzy).
Ok, ты работаешь над проектом. Менеджер спрашивает тебя, готов ли ты поставить свою зарплату, что твой многопоточный кусок кода, в котором ты не используешь ф-ций синхронизации, а полагаешься на "fuzzy" семантику volatile, заработает на не Intel SMP системе? Что ты ему ответишь?
Здравствуйте, eao197, Вы писали:
W>>А зачем здесь синхронизация? Один поток пишет значение переменной, другой читает. Главное, чтобы значение было записано атомарно. Это в данном случае для x86 гарантируется.
E>Боюсь, что именно из-за таких предположений весь сыр бор вокруг volatile и начался. Я считаю, что есть принципиальные вещи: выделеную память нужно освобождать, открытые файлы закрывать, после входа в критическую секцию нужно выйти оттуда, ..., когда одна нить читает то, что другая может переписать, то это нужно синхронизировать. Это вопрос принципа (с моей точки зрения). И если ему следовать, то: E>- мы не зависим от volatile или не volatile; E>- мы не зависим от аппаратной платформы (могут быть какие-нибудь специализированные процессоры, на которых извлечение одного байта из памяти может не быть атомарной операцией, а требовать дополнительных арифметических операций/сдвигов); E>- мы не зависим от типа данных. Сейчас у нас переменная go -- это bool. Завтра -- это long int, в котором указано, сколько итераций нам еще разрешают сделать. После завтра -- структура, в которой описывается, при каких условиях нужно выходить, а при каких условиях еще можно провести пару-тройку итераций. E>А все из-за того, что изначально мы защитились синхронизацией. И не надеялись на особенности конкретной платформы и конткретного компилятора.
Для того, чтобы не зависеть от платформы, нужно разделять платформенно-зависимый и платформенно-независимый код. А вот платформенно-зависимые примитивы, например, средства синхронизации могут быть реализованы отдельно под некоторые платформы максимально эффективным способом. И если под x86 работа с 32-битными числами атомарна, то в платформенно-зависимых частях кода нужно это использовать.
W>>С другой стороны, а если бы рабочих потоков было, скажем, 4 вместо 1. И все читали бы одну и ту же переменную. Тогда использование синхронизации могло бы привести к ненужным потерям производительности. E>За безопасность в многопоточности нужно платить. Некоторые из-за безопасности на более медленные языки программирования переходят Из-за того, что лишний раз delete вызвать лень
Да, но зачем платить, когда это не нужно? Например, критические секции, если не надо останавливать поток, обходятся без перехода в режим ядра. Другой пример — паттерн double-checked locking, при реализации которого, кстати, может понадобиться volatile.
The problem exists, because the C++ standard allows reordering of C++ statements,
as long as the observable behaviour stays the same — with the bad side
effect that the "observable behavior" in C++ is not multi-threading
aware and this finally leads to Scott's conclusion: "There is no
portable way to implement DCLP in C++".
[/c]