Являются ли Delphi простыми типами потокобезопасными?

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

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

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

Ответ 1

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

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

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

Ответ 2

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

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

Ответ 3

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

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

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

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

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

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

Например,

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;

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

Ответ 4

Нет, они не являются потокобезопасными, вы должны обращаться к таким переменным, используя, например, критический раздел, используя 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);