Для контроля доступности используйте setHomeAsUpEnabled()
.
Я не знаю ни одного подхода MREW без блокировки (или микроблокировки, как в приведенном выше примере), который можно было бы реализовать в коде Intel86.
Для небольших (быстро истекающих) блокировок отлично работает метод вращения из OmniThreadLibrary :
type
TOmniMREW = record
strict private
omrewReference: integer; //Reference.Bit0 is 'writing in progress' flag
public
procedure EnterReadLock; inline;
procedure EnterWriteLock; inline;
procedure ExitReadLock; inline;
procedure ExitWriteLock; inline;
end; { TOmniMREW }
procedure TOmniMREW.EnterReadLock;
var
currentReference: integer;
begin
//Wait on writer to reset write flag so Reference.Bit0 must be 0 than increase Reference
repeat
currentReference := omrewReference AND NOT 1;
until currentReference = InterlockedCompareExchange(omrewReference, currentReference + 2, currentReference);
end; { TOmniMREW.EnterReadLock }
procedure TOmniMREW.EnterWriteLock;
var
currentReference: integer;
begin
//Wait on writer to reset write flag so omrewReference.Bit0 must be 0 then set omrewReference.Bit0
repeat
currentReference := omrewReference AND NOT 1;
until currentReference = InterlockedCompareExchange(omrewReference, currentReference + 1, currentReference);
//Now wait on all readers
repeat
until omrewReference = 1;
end; { TOmniMREW.EnterWriteLock }
procedure TOmniMREW.ExitReadLock;
begin
//Decrease omrewReference
InterlockedExchangeAdd(omrewReference, -2);
end; { TOmniMREW.ExitReadLock }
procedure TOmniMREW.ExitWriteLock;
begin
omrewReference := 0;
end; { TOmniMREW.ExitWriteLock }
Я только что заметил здесь возможную проблему с выравниванием - код должен проверять, что omrewReference имеет 4 выравнивания. Извещу автора.
Просто дополнение - то, что вы здесь видите, обычно известно как Указатели опасности . Понятия не имею, можно ли сделать что-то подобное в Delphi.
Прошло некоторое время с тех пор, как я запачкал руки в Delphi, поэтому проверьте это перед использованием, но ... из памяти вы можете получить поведение с подсчетом ссылок, если вы используете интерфейс и реализация с использованием TInterfacedObject.
type
IDataClass = interface
function GetSome: integer;
function GetData: double;
property Some: integer read GetSome;
property Data: double read GetData;
end;
TDataClass = class(TInterfacedObject, IDataClass)
private
FSome: integer;
FData: double;
protected
function GetSome: integer;
function GetData: double;
public
constructor Create(ASome: integer; AData: double);
end;
Затем вместо этого вы делаете все свои переменные типа ISomeData (смешивание ISomeData и TSomeData - очень плохая идея ... вы легко получаете проблемы со счетчиком ссылок).
В основном это приведет к тому, что счетчик ссылок будет автоматически увеличивается в коде вашего считывателя, где он загружает локальную ссылку на данные, и уменьшается, когда переменная покидает область видимости, и в этот момент она будет освобождена оттуда.
Я знаю, что дублировать API-интерфейс немного утомительно. ваш класс данных в интерфейсе и реализация класса, но это самый простой способ добиться желаемого поведения.
У меня есть для вас потенциальное решение; это позволяет новым читателям начать работу в любое время, пока писатель не захочет писать. Затем писатель ждет, пока читатели закончат, и выполняет запись. После того, как письмо будет написано, читатели могут прочитать еще раз.
Кроме того, это решение не требует блокировок или мьютексов, но требует атомарной операции проверки и установки. Я не знаю Delphi и написал свое решение на Lisp, поэтому постараюсь описать его псевдокодом.
(CAPS - это имена функций, все эти функции принимают и не возвращают аргументов)
integer access-mode = 1; // start in reader mode.
WRITE loop with current = accessmode,
with new = (current & 0xFFFFFFFe)
until test-and-set(access-mode, current to new)
loop until access-mode = 0;
ENDWRITE assert( access-mode = 0)
set access-mode to 1
READ loop with current = ( accessmode | 1 ),
with new = (current + 2),
until test-and-set(access-mode, current to new)
ENDREAD loop with current = accessmode
with new = (current - 2),
until test-and-set(access-mode, current to new)
Для использования считыватель вызывает READ перед чтением и ENDREAD по завершении. Одинокий писатель вызывает WRITE перед записью и ENDWRITE по завершении.
Идея состоит в том, что целое число, называемое режимом доступа, содержит логическое значение в младшем бите и счетчик в старшие биты. WRITE устанавливает бит в 0, а затем вращается до тех пор, пока режим доступа ENDREAD не станет достаточным для обратного отсчета до нуля. Endwrite устанавливает режим доступа обратно в 1. ЧИТАТЬ ИЛИ записывает текущий режим доступа с 1, так что их проверка и установка пройдут, только если младший бит был высоким с самого начала. Я складываю и вычитаю на 2, чтобы оставить младший бит в покое.
Чтобы подсчитать количество читателей, просто сдвиньте режим доступа вправо на единицу.