Безопасно ли устанавливать логическое значение в потоке из другого? - PullRequest
9 голосов
/ 08 марта 2012

Мне интересно, безопасен ли следующий (псевдо) код. Я знаю о флаге Termination, но мне нужно установить своего рода флаг отмены при рекурсивной операции поиска из основного потока и сохранить рабочий поток работающим. Там я также проверю свойство Termination, чего нет в этом псевдокоде.

type
  TMyThread = class(TThread)
  private
    FCancel: Boolean;
    procedure RecursiveSearch(const ItemID: Integer);
  protected
    procedure Execute; override;
  public
    procedure Cancel;
end;

procedure TMyThread.Cancel;
begin
  FCancel := True;
end;

procedure TMyThread.Execute;
begin
  RecursiveSearch(0);
end;

procedure TMyThread.RecursiveSearch(const ItemID: Integer);
begin
  if not FCancel then
    RecursiveSearch(ItemID);  
end;

procedure TMainForm.ButtonCancelClick(Sender: TObject);
begin
  MyThread.Cancel;
end;

Безопасно ли таким образом устанавливать логическое свойство FCancel внутри потока? Не противоречит ли это чтению этого флага в процедуре RecursiveSearch, пока нажата кнопка в главной форме (основной поток)? Или мне придется добавить, например, критический раздел для чтения и записи этого значения?

Большое спасибо

Ответы [ 4 ]

17 голосов
/ 08 марта 2012

Это совершенно безопасно сделать это. Чтение чтения всегда будет читать либо истину, либо ложь. Разрыва не будет, потому что Boolean - это всего лишь один байт. Фактически то же самое верно для выровненного 32-битного значения в 32-битном процессе, то есть Integer.

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

8 голосов
/ 08 марта 2012

Запись в логическое поле из разных потоков является поточно-ориентированной - это означает, что операция записи является атомарной.Ни один наблюдатель поля никогда не увидит «частичное значение», поскольку значение записывается в поле.При больших типах данных частичная запись является реальной возможностью, потому что для записи значения в поле требуются несколько инструкций ЦП.

Таким образом, фактическая запись логического значения не является проблемой безопасности потока.Однако то, как наблюдатели используют это логическое поле, может быть проблемой безопасности потока.В вашем примере единственным видимым наблюдателем является функция RecursiveSearch, и ее использование значения FCancel довольно просто и безвредно.Наблюдатель за состоянием FCancel не изменяет состояние FCancel, поэтому это прямая / ациклическая зависимость типа производитель-потребитель.

Если вместо этого код использовал логическое поле для определения необходимости однократной операциичтобы сделать это, простого чтения и записи в логическое поле будет недостаточно, поскольку наблюдатель булевого поля также должен изменить поле (чтобы отметить, что была выполнена одноразовая операция).Это цикл чтения-изменения-записи, и он небезопасен, когда два или более потоков выполняют одинаковые шаги в нужное время.В этой ситуации вы можете установить блокировку мьютекса на одноразовую операцию (и проверку и обновление логического поля) или использовать InterlockedExchange для обновления и тестирования логического поля без мьютекса.Вы также можете переместить одноразовую операцию в конструктор статического типа, и вам не придется поддерживать какие-либо блокировки самостоятельно (хотя .NET может использовать блокировки за кулисами для этого).

4 голосов
/ 08 марта 2012

Я согласен, что запись логического значения из одного потока и чтение из другого потока безопасна. Тем не менее, будьте осторожны с , увеличивающим - это не атомарно и может привести к явно небогатому состоянию гонки в вашем коде в зависимости от реализации. Инкремент / Уменьшение обычно превращается в три отдельные машинные инструкции - загрузка / вкл / хранение.

Это то, для чего предназначены вызовы API-интерфейсов InterlockedIncrement, InterlockedDecrement и InterlockedExchange Win32 - чтобы 32-разрядные приращения, уменьшения и загрузки выполнялись атомарно без отдельного объекта синхронизации.

2 голосов
/ 08 марта 2012

Да, это безопасно, вам нужно использовать Критические разделы только тогда, когда вы читаете или пишете из / в другой поток, в том же потоке это безопасно.

КСТАТИ. как у вас определен метод RecursiveSearch, если (FCancel = False), то вы получите переполнение стека (:

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...