#multithreading #delphi #synchronization
#многопоточность #delphi #синхронизация
Вопрос:
У меня есть многопоточная программа на Delphi, создающая несколько экземпляров некоторых классов, и я хочу, чтобы у каждого экземпляра класса был свой собственный экземпляр TMultiReadExclusiveWriteSynchronizer для использования в методах get и set определенных свойств.
Например. Вот часть модуля, в котором я использую TMultiReadExclusiveWriteSynchronizer в одном классе:
interface
TSGThread=class(TThread)
private
fWaiting:boolean;
function getWaiting:boolean;
procedure setWaiting(value:boolean);
public
property waiting:boolean read getWaiting write setWaiting;
end;
implementation
var syncWaiting:TMultiReadExclusiveWriteSynchronizer;
function TSGThread.getWaiting:boolean;
begin
syncWaiting.BeginRead;
result:=fWaiting;
syncWaiting.EndRead;
end;
procedure TSGThread.setWaiting(value:boolean);
begin
syncWaiting.BeginWrite;
fWaiting:=value;
syncWaiting.EndWrite;
end;
initialization
syncWaiting:=TMultiReadExclusiveWriteSynchronizer.Create;
finalization
syncWaiting.Free;
end.
Проблема с этим заключается в том, что модуль создает один экземпляр TMultiReadExclusiveWriteSynchronizer, который затем используется несколькими экземплярами TSGThread.
Синхронизатор контролирует доступ только к закрытому полю TSGThread.
Поток A может изменять поле в потоке B, используя общедоступное свойство, отсюда и необходимость в синхронизаторе, но в каждом потоке должен быть отдельный синхронизатор, чтобы потокам не приходилось ждать друг друга, если они изменяют свои собственные свойства.
В справке Delphi сказано «создать глобальный экземпляр TMultiReadExclusiveWriteSynchronizer», но строго ли необходимо, чтобы он всегда был глобальным?
Если класс защищает доступ только к своим собственным свойствам, будет ли синхронизация работать с экземпляром TMultiReadExclusiveWriteSynchronizer в закрытом поле?
Комментарии:
1. После просмотра ответов ниже я успешно протестировал свои классы с синхронизаторами в закрытых полях.
Ответ №1:
В справке сказано следующее:
Создайте глобальный экземпляр
TMultiReadExclusiveWriteSynchronizer
, связанный с глобальной памятью, которую вы хотите защитить.
Но у вас нет глобальной памяти. У вас есть память, зависящая от объекта потока, поэтому создайте объекты синхронизации для каждого объекта потока. Вы можете создать столько, сколько вам нужно. Создайте по одному для каждого общего элемента, к которому вы хотите получить индивидуальный доступ и защитить.
Ответ №2:
Это немного не по теме, но в приведенном вами примере синхронизатор практически бесполезен. Логическое значение не может быть частично прочитано / записано при переключении контекста потока.
Также, в вашем примере, код, подобный этому
sgThread.Waiting := not sgThread.Waiting;
может произойти сбой.
Теперь вернемся к теме. Область действия TMultiReadExclusiveWriteSynchronizer должна быть такой же большой, как ресурс, который он защищает. Поскольку вы хотите защитить частное поле объекта, вы можете объявить TMultiReadExclusiveWriteSynchronizer также как частное поле. (Вы защищаете доступ к закрытому полю, а не доступ к свойству)
Комментарии:
1. Я использовал синхронизатор, потому что недавно выполнил некоторый код для обработки потоков на Java, прочитал о спецификаторе Java «volatile», у меня сложилось впечатление, что его рекомендуется использовать всякий раз, когда отдельные потоки могут читать / записывать одно и то же поле, и что TMultiReadExclusiveWriteSynchronizer от Delphi был достойным эквивалентом.
2. Что касается sgThread. Ожидание: = не sgThread. Ожидание; ситуация, не будет ли оценена правая сторона (завершение метода get) перед вызовом метода set свойства?
3. Да, правая сторона будет оценена после вызова метода set. Проблема в том, что другой поток может фактически прочитать и изменить значение между Get и Set, что может привести к непредсказуемым результатам. Если у вас есть 2 потока, которые выполняют строку выше… допустим, по 10 раз каждый. В конце не всегда будет один и тот же результат.
Ответ №3:
Нет строгой необходимости в том, чтобы он был глобальным. Необходимо, чтобы каждый доступ проходил через синхронизатор, чтобы поддерживать потокобезопасность. Один из простых способов включить это — сделать синхронизатор глобальной переменной, но есть и другие способы справиться с этим.
Комментарии:
1. Принял это в качестве ответа только потому, что оно появилось первым, но ответ Роба не менее полезен.