Модели потоков. Понятие о синхронизации (окончание)

   
На этом шаге мы рассмотрим необходимость использования синхронизации.

   
Для лучшего понимания механизма синхронизации и обоснования необходимости в ней рассмотрим следующий пример. Предположим, что имеется постоянный буфер в памяти, куда один из потоков заносит дату в текстовом
виде, а другой считывает это значение и использует для каких-либо вычислений. Рассмотрим процесс, который реально может происходить при такой конфигурации.
Начальное значение буфера:

  12 December 2012
  • Поток, который должен считывать значение, начал чтение и считал первые 8 символов: 12 Decem.
  • Происходит вытеснение первого потока, и второй поток заносит новое значение: 15 February 2013.
  • Через некоторое время восстанавливается первый поток, который продолжает чтение буфера с прерванного места и считывает оставшуюся часть: агу 2013.
  •    
    Значение даты, которое считал первый поток (12 Decemary 2013), абсолютно бессмысленно. Если, например, занести данное значение в базу данных, то очевидны трудности с последующей интерпретацией этой строки. К
    сказанному следует добавить, что если хранить эту переменную не в постоянном буфере, а использовать переменную типа string, могут происходить нарушения в защите памяти.

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

       
    Метод Synchronize, который определен в классе TThread, обеспечивает синхронизацию фонового и главного потоков. В качестве параметра этот метод использует адрес другого метода - например,
    DoSomething. При выполнении кода, реализованного в методе DoSomething, главный поток не прерывает фоновый до завершения выполнения метода DoSomething. В коде DoSomething можно
    обращаться к общим переменным, не опасаясь описанных выше коллизий. Ясно, что метод Synchronize нельзя использовать для синхронизации доступа к данным из двух фоновых потоков. Для этого требуются специальные
    объекты - критические секции, семафоры, мыотексы, сообщения. О них будет рассказано в следующих шагах.

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

       
    В классе TThread определен метод WaitFor. Вызов этого метода означает прекращение выполнения кода главного потока до окончания работы фонового. Создадим новый проект, поместим па форму две кнопки,
    объявим класс ТМуThread - потомок класса TThread и реализуем следующий код:

    procedure TMyThread.Execute;
    begin
      // Модель реального кода вычислений
      repeat
        Inc(FL);
        Sleep(100);
        Beep;
      until (FL >= 30) or Terminated;
    end;
    
    procedure TForm1.Button1Click(Sender: TObject);
    begin
      if FS <> nil then Exit;
      FS := TMyThread.Create(True);
      with FS do
      begin
        FreeOnTerminate := True;
        OnTerminate := ThreadDone;
        Resume;
      end;
    end;
    
    procedure TForm1.Button2Click(Sender: TObject);
    begin
      if FS <> nil then FS.WaitFor;
      ShowMessage('Все');
    end;
    
    procedure TForm1.ThreadDone(Sender: TObject);
    begin
      FS := nil;
    end;
    

    Текст этого приложения можно взять здесь (214,0 Кб).

       
    Данный код можно тестировать в операционной системе Windows 95/98/NT, но не Windows 2000/XP. При щелчке на первой кнопке начинаются вычисления - это контролируется на слух по писку динамиков. Если
    в процессе вычислений щелкнуть на второй кнопке, то сообщение "Все" появится не сразу, а только после окончания писка в динамиках.

       
    Вследствие ошибки в VCL при реализации метода TThread.WaitFor, а также изменений в операционной системе Windows 2000/XP, вызов деструктора класса TThread осуществляется до того, как
    завершится код метода TThread.WaitFor. Это приводит к появлению исключения EOSExeption с кодом 6 - Invalid Handle. Поэтому если может быть вызван метод WaitFor, то свойство
    FreeOnTerminate должно иметь значение False. В этом случае запрещено вызывать деструктор в явном виде из обработчика событий OnTerminate - он будет вызываться раньше завершения метода WaitFor.

       
    На следующем шаге мы рассмотрим взаимосвязи потоков и апартаментов.



    Вы можете оставить комментарий, или Трекбэк с вашего сайта.

    Оставить комментарий