Реальные опасности записи 2+ потоков / чтения переменной - PullRequest
7 голосов
/ 08 мая 2011

Какова реальная опасность одновременного чтения / записи для одной переменной?

Если я использую один поток для записи переменной, а другой - для чтения переменной в цикле while, и нет опасности, если переменная читается во время записи и используется старое значение, что еще здесь представляет опасность?

Может ли одновременное чтение / запись вызвать сбой потока или что происходит на низком уровне, когда происходит точное одновременное чтение / запись?

Ответы [ 8 ]

5 голосов
/ 10 мая 2011

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

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

например. с двумя потоками, увеличивающими переменную, вы можете пропустить счет, как описано в моей статье на devx: http://www.devx.com/cplus/Article/42725

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

3 голосов
/ 08 мая 2011

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

Критическая гонка возникает, когда порядок, в котором изменяются внутренние переменные, определяет конечное состояние, в котором конечный автомат окажется.

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

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

2 голосов
/ 08 мая 2011

Два потока, читающие одно и то же значение, не являются проблемой вообще.

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

Если у двух потоков есть доступ к одним и тем же данным, рекомендуется + блокировка (мьютекс, семафор).

НТН

Mario

1 голос
/ 08 мая 2011

Худшее, что случится, зависит от реализации.Существует так много полностью независимых реализаций pthreads, работающих на разных системах и оборудовании, что я сомневаюсь, что кто-нибудь знает обо всех них все.

Если p не является указателем на изменчивый, то я думаю,что компилятор для соответствующей реализации Posix разрешил превращаться:

while (*p == 0) {}
exit(0);

В единственную проверку *p с последующим бесконечным циклом, который не утруждает себя просмотром значения*p вообще.На практике это не так, поэтому вопрос заключается в том, хотите ли вы программировать в соответствии со стандартом или программировать на недокументированное наблюдаемое поведение используемых вами реализаций.Последний обычно работает для простых случаев, и затем вы строите код, пока не сделаете что-то достаточно сложное, чтобы оно неожиданно не сработало.

На практике, в многопроцессорной системе, которая не имеет связногоКэши памяти, может пройти очень много времени, прежде чем цикл while увидит изменения, сделанные на другом процессоре, потому что без барьеров памяти он никогда не сможет обновить свое кэшированное представление основной памяти.Но у Intel есть согласованные кэши, поэтому, скорее всего, вы лично не увидите задержек, достаточных для того, чтобы о них заботиться.Если какой-то плохой обманщик попытается запустить ваш код на более экзотической архитектуре, он может в конечном итоге исправить это.

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

  • p указывает на неатомарный тип, например long long в типичной 32-разрядной архитектуре.
  • long long в этой системе имеетпредставления ловушек, например, потому что у него есть бит дополнения, используемый в качестве проверки четности.
  • запись в *p наполовину завершена, когда чтение происходит
  • , при полусфере обновлено несколькобитов значения, но еще не обновил бит четности.

Удар, неопределенное поведение, вы читаете представление прерывания.Возможно, что Posix запрещает определенные представления ловушек, которые допускает стандарт C, и в этом случае long long может быть недопустимым примером для типа *p, но я ожидаю, что вы можете найти тип, для которого разрешены представления ловушек.

0 голосов
/ 08 мая 2011

Результат не определен.

Рассмотрим этот код:

global int counter = 0;


tread()
{
   for(i=0;i<10;i++)
   {
       counter=counter+1;
   }
}

Проблема в том, что если у вас N потоков, результатом может быть что угодно между 10 и N * 10. Это потому, что может случиться так, что все ступени читают одно и то же значение, увеличивают его, а затем записывают значение +1 обратно. Но вы спросили, можете ли вы аварийно завершить работу программы или оборудования.
Это зависит. В большинстве случаев неправильные результаты бесполезны.

Для решения этой проблемы блокировки вам понадобится мьютекс или семафор.

Мьютекс - это замок для кода. В верхнем регистре вы должны заблокировать часть кода в строке

counter = counter+1;

Где семафор является замком для переменной

counter 

То же самое, что и для решения проблем того же типа.

Проверьте наличие этих инструментов в вашей библиотеке протектора.

http://en.wikipedia.org/wiki/Mutual_exclusion

0 голосов
/ 08 мая 2011

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

0 голосов
/ 08 мая 2011
  • Вы можете увидеть частичное обновление (например, вы можете увидеть переменную long long, половина из которой берется из нового значения, а другая половина - из старого значения).не гарантируется увидеть новое значение, пока вы не используете барьер памяти (pthread_mutex_unlock() содержит неявный барьер памяти).
0 голосов
/ 08 мая 2011

Если переменная, записываемая в и из, не может быть обновлена ​​или прочитана атомарно, тогда читатель может получить поврежденное «частично обновленное» значение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...