Вопрос вчера об удвоенном - проверенная блокировка запустила цепочку мыслей, которые оставили меня не уверенным в простой ситуации. В следующем коде, это возможный совершить нападки printf
из “Больше в синхронизации”? В этом простом примере значения, вероятно, были бы на той же строке кэша, таким образом, я думаю, что это было бы менее вероятно (предположение, что возможность> 0% для начала).
Если ответ, “Нет, это не возможно”. затем мой последующий вопрос, скорее очевидно: почему нет? Пока я не запутал свои мысли и вчера перенес аксель многопоточности, я предположил, что код будет безопасен. Но теперь я задаюсь вопросом, что предотвращает устаревшее чтение от кэша для одной из переменных pa
или pb
. И это имело бы значение если pa, pb
указанный простые глобальные целочисленные переменные, а не malloc’d память? Вызов WaitForSingleObject обеспечивает барьер памяти? Или указатели должны быть объявлены энергозависимые? Столько вопросов, так мало предложений.
Обновление: Я наконец определил местоположение информации, которая действительно конкретно говорит, что функционирует, которые сигнализируют, что объекты синхронизации действительно используют барьеры памяти. Это должно было быть очевидно, но я испытывал затруднения при нахождении категорического ответа. Таким образом, я могу еще раз ввести меня в заблуждение, я понимаю все это.
int i1 = 0;
int i2 = 0;
int reads = 0;
int done = 0;
int *pa = NULL;
int *pb = NULL;
HANDLE hSync = NULL;
DWORD WriteThread( LPVOID pvParam )
{
while( !done )
{
WaitForSingleObject( hSync, INFINITE );
(*pa)++;
(*pb)++;
ReleaseSemaphore( hSync, 1, NULL );
}
return 0;
}
DWORD ReadThread( LPVOID pvParam )
{
while( !done )
{
WaitForSingleObject( hSync, INFINITE );
if ( *pa != *pb )
{
printf( "No longer in sync: %d, %d\n", *pa, *pb );
exit( 1 );
}
ReleaseSemaphore( hSync, 1, NULL );
reads++;
}
return 0;
}
int main( int argc, char* argv[] )
{
DWORD dwID;
// malloc'd memory
pa = (int*)malloc( sizeof( int ));
pb = (int*)malloc( sizeof( int ));
// Is a simple global variable different?
//pa = &i1;
//pb = &i2;
*pa = 0;
*pb = 0;
hSync = CreateSemaphore( NULL, 1, 1, NULL );
CreateThread( NULL, 0, WriteThread, NULL, 0, &dwID );
CreateThread( NULL, 0, ReadThread, NULL, 0, &dwID );
while ( *pa < 1000000 )
Sleep( 1 );
done = 1;
return 0;
}
Неважно, где находится память, и если бы все дело было в когерентности кэша, то объявление переменных волатильными ничего бы не сделало. Семантика волатильности не является ни необходимой, ни достаточной для потокобезопасности, не используйте ее!
На уровне Си/Си++ па и pb могут кэшироваться в регистрах, но после любого вызова функции они будут считаться просроченными. На уровне CPU все функции ожидания используют барьеры, чтобы убедиться, что все работает, как ожидается.
.