Общее правило: всё верно, при работе с расшаренными данными СОХРАНИ ИХ СНАЧАЛА, ЧЁРТ ПОБЕРИ, В ЛОКАЛЬНУЮ ПЕРЕМЕННУЮ. (Обычно после такого сохранения можно и блокировку отпустить.)
Проще всего с критическими секциями. Но можно сделать и без них:
Вариант 1, сложный.
Через Interlocked*.Функция
InterlockedCompareExchange(
var target: pointer; value: pointer; comparand: pointer): pointer (
https://ru.wikipedia.org/wiki/Сравнение_с_обменом) выполняет атомарный эквивалент
- Код: Выделить всё
result := target;
if target = comparand then target := value;
Функция
InterlockedExchange(
var target: pointer; value: pointer): pointer выполняет атомарный эквивалент
- Код: Выделить всё
result := target;
target := value;
Их можно использовать в сочетании с тем фактом, что string хранится как указатель. Но нужно будет вручную проследить за счётчиками ссылок, т. к. функции работают с сырыми указателями и компилятор не вставляет магию с fpc_addref / fpc_release, вставляемую при обычных операциях, когда
- Код: Выделить всё
var a, b: string;
a := b;
расписывается в
- Код: Выделить всё
Finalize(a);
fpc_addref(b);
pointer(a) := pointer(b);
- Код: Выделить всё
type
TMyThread = class(TThread)
private
fDataString: string;
protected
procedure Execute; override;
public
procedure SetDataString(const s: string);
function FetchDataString: string;
end;
function fpc_addref(data, typeInfo: pointer): SizeInt; [external name 'FPC_ADDREF'];
procedure TMyThread.Execute;
var
ds: string;
begin
while not Terminated do
begin
// ...
// Блок обработки внешних данных
ds := FetchDataString;
if ds.Length > 0 then
begin
// ...
end;
// ...
Sleep(1);
end;
end;
procedure TMyThread.SetDataString(const s: string);
begin
if InterlockedCompareExchange(pointer(fDataString), pointer(s), nil) = nil then
// Если старое значение равно nil — сырой указатель s скопирован в fDataString, нужно аддрефнуть.
fpc_addref(@s, TypeInfo(s))
else
// Иначе InterlockedCompareExchange ничего не сделала. Это если повторные SetDataString нужно игнорировать.
;
end;
function TMyThread.FetchDataString: string;
begin
Finalize(result); // result не обязан быть пустым, а нам нужен пустой, т. к. в следующей строчке он буквально затирается.
pointer(result) := InterlockedExchange(pointer(fDataString), nil);
// ничего AddRef'ать не нужно, fDataString занулена.
end;
(Можно извернуться и сделать без fpc_addref, чтобы это не было FPC-only, но это будет выглядеть ещё более магически.)
В этом варианте FetchDataString «крадёт» fDataString, зануляя старое значение.
Вариант 2. Просто забить.Вообще-то присваивание примитивных типов, в том числе указателей (при некоторых оговорках, типа соблюдения выравнивания)
атомарно. То есть если у тебя в коде указатель 11111111 меняется на 22222222, то другой поток и без синхронизации вскоре увидит эту же смену 11111111 на 22222222 — он никогда не прочитает 11222222, или мусор, или снова внезапное 11111111 после того, как уже увидел 22222222, и т. д.
В случае со строками, хотя к ним добавляется магия компилятора, она также потокобезопасна (и работа со счётчиками ссылок, и GetMem/FreeMem), поэтому товарища выше не слушай —
всё будет работать. Нужно просто сохранить строку в локальную переменную, чтобы она не изменилась у нас под носом.
- Код: Выделить всё
type
TMyThread = class(TThread)
private
fDataString: string;
protected
procedure Execute; override;
public
procedure SetDataString(const s: string);
end;
procedure TMyThread.Execute;
var
ds: string;
begin
while not Terminated do
begin
// ...
// Блок обработки внешних данных
ds := fDataString;
if ds.Length > 0 then
begin
// ...
end;
// ...
Sleep(1);
end;
end;
procedure TMyThread.SetDataString(const s: string);
begin
fDataString := s;
end;
Если вместе со строкой должно обязательно устанавливаться что-то ещё — флаг «somethingChanged» какой-нибудь, или 2 таких строки должны установиться одновременно, то без критической секции всё же не обойтись. Но если у тебя реально 1 строка, или даже не одна, но гонки не критичны (неодновременная установка 2 строк ничего не сломает) — можно и так.
P. S. Ещё одно правило: по возможности не используй Sleep и уж точно НИКОГДА не используй Sleep(1), и даже Sleep(100). Используй события (RTLEventCreate или SyncObjs.TEventObject) и ожидание событий, опять же, по возможности без таймаута (timeout=INFINITE).