Являются ли простые типы Delphi поточно-ориентированными? - PullRequest
12 голосов
/ 30 марта 2011

Я объявил две глобальные переменные:

var
  gIsRunning: Boolean = False;
  gLogCounter: Integer = 0;

Эти переменные записываются только в основной поток и читаются в других потоках. В этом случае эти переменные потокобезопасны?

Ответы [ 4 ]

49 голосов
/ 30 марта 2011

Вы, вероятно, говорите об атомных переменных.Целочисленные и логические переменные являются атомарными.Логические (байты) всегда атомарные, целые (32-битные) атомарные, потому что компилятор правильно их выравнивает.

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

Но атомарность не означает безопасность потока - вы можете легко написать небезопасный код с атомарными переменными.Сама переменная не может быть потокобезопасной - только код в целом может быть (или нет) потокобезопасным.

13 голосов
/ 30 марта 2011

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

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

11 голосов
/ 30 марта 2011

Простые типы являются «поточно-ориентированными», если они могут быть прочитаны за один раз (или записаны за одну запись) из памяти.Я не уверен, определяется ли он шириной шины памяти процессора или их «целочисленным» размером (32 бита против 64-битного процессора).Может быть, кто-то еще может уточнить эту часть.

Я знаю, что размер чтения в настоящее время составляет не менее 32 бит.(Раньше в Intel 286 дней это было только 8 битов за раз).

Хотя об этом нужно знать одну вещь.Даже если он может читать 32 бита за раз, он не может начать чтение только с любого адреса.Это должно быть кратно 32 битам (или 4 байта).Таким образом, даже целое число может быть прочитано в 2 последующих чтениях, если оно не выровнено по 32 битам.К счастью, компилятор автоматически выровняет почти все поля по 32 битам (или даже 64 битам).

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

Из-за своего размера int64 также не является поточно-ориентированной.То же самое можно сказать о большинстве плавающих типов.(За исключением Single, я верю).

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

Например,

var
  LastGoodValueTested : Integer

procedure TestValue(aiValue : Integer);
begin
  if ValueGood(aiValue) then
    LastGoodValue := aiValue
end;

здесь вы можете вызвать подпрограмму TestValue из нескольких потоков, и вы не станете повреждать переменные LastGoodValueTested.Может случиться так, что значение, записанное в переменную, не будет самым последним.(Если происходит переключение контекста потока между ValueGood (aiValue) и назначением).Таким образом, в зависимости от потребностей, это может / не может быть приемлемо.

Теперь,

var     
  gLogCounter: Integer = 0;

procedure Log(S : string);
begin
  gLogCounter := gLogCounter + 1;
end;

Здесь вы можете повредить счетчик, потому что это неодинарная операция.Сначала вы читаете переменную.Затем добавьте 1 к нему.Тогда вы сохраните его обратно.Переключение контекста потока может произойти в середине этой операции.Так что это случай, который требует синхронизации.

В этом случае его можно переписать в

procedure Log(S : string);
begin
  InterlockedIncrement(gLogCounter);
end;

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

8 голосов
/ 30 марта 2011

Нет, они не являются потокобезопасными , вы должны получить доступ к таким переменным, используя, например, критическую секцию, используя InitializeCriticalSection, EnterCriticalSection и LeaveCriticalSection функции

//declaration of your global variables 
var 
   MyCriticalSection: TRTLCriticalSection;
   gIsRunning: Boolean;
   gLogCounter: Integer;

//before the threads starts
InitializeCriticalSection(MyCriticalSection); 

//Now in your thread 
  EnterCriticalSection(MyCriticalSection); 
//Here you can make changes to your variables. 
  gIsRunning:=True;
  inc(gLogCounter); 
//End of protected block
LeaveCriticalSection(MyCriticalSection); 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...