На этом шаге мы рассмотрим необходимость использования синхронизации.
Для лучшего понимания механизма синхронизации и обоснования необходимости в ней рассмотрим следующий пример. Предположим, что имеется постоянный буфер в памяти, куда один из потоков заносит дату в текстовом виде, а другой считывает это значение и использует для каких-либо вычислений. Рассмотрим процесс, который реально может происходить при такой конфигурации. Начальное значение буфера:
12 December 2012
Значение даты, которое считал первый поток (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;
Данный код можно тестировать в операционной системе 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.
На следующем шаге мы рассмотрим взаимосвязи потоков и апартаментов.