Данная статья посвящена моим экспериментам с семафорами в Windows программировании. В принципе, после изучения потоков, критических секций, событий, мютексов – осваивать семафоры было относительно легко, но всё же нюансы были.
Что такое семафоры?
Это объекты, которые ограничивают доступ к участку кода несколькими потоками. Потоки могут быть в одном или нескольких процессах. В отличие от мютексов при использовании семафоров – с участками кода могут работать несколько потоков, а в мютексах только один. В остальном мютексы и семафоры похожи.
Практика
Напишем небольшую программу, которая будет иллюстрировать действие семафоров, выглядеть она будет вот так…
Первый поток будет записывать данные – остальные -считывать, подхватывать на том месте, где было значение ProgressBar1.Position в момент нажатия на Start2, например, и выставлять ProgressBar2.Position в такое же значение, а далее – следовать за ProgressBar1.Position по пятам. Каждый ProgressBar в отдельном потоке.
Я заметил, что если использовать код без семафоров, то если запустить 4 потока сразу – начинаются жуткие тормоза. Возможно это не оптимизирован код, но это как раз то, что нужно для иллюстрации семафоров.
Итак перейдем к написанию кода.
Добавим в uses syncobjs
1 2 |
uses ...syncobjs |
далее, в разделе type создадим 4 класса потоков
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
TFirstThread=class(TThread) protected procedure execute; override; end; TSecondThread=class(TThread) protected procedure execute; override; end; TThirdThread=class(TThread) protected procedure execute; override; end; TFourthThread=class(TThread) protected procedure execute; override; end; |
Далее, опишем глобальные переменные
1 2 3 4 5 6 7 8 |
var Form: TForm; FirstThread:TFirstThread; SecondThread:TSecondThread; ThirdThread:TThirdThread; FourthThread:TFourthThread; Semaphore:TSemaphore; |
В FormCreate создадим Семафор
1 2 3 4 5 6 7 8 9 10 |
procedure TForm2.FormCreate(Sender: TObject); begin Semaphore := TSemaphore.Create( nil, 1, 1, 'mysemaphore', false ); end; |
Здесь обратите внимание, второй и третий параметры поставлены в единицу. Это значит, что мы можем максимально допустить ещё один поток. То есть, у нас будут заполняться 2 шкалы. Одна пишущая, другая читающая. Нажатие на кнопки Start оставшихся 2 шкал ничего не дадут. Чтобы допустить большее число потоков – нужно изменить эти числа, на необходимые.
Процедура для первого пишущего потока
1 2 3 4 5 6 7 8 9 10 |
Procedure ProgressWrite(PB:TProgressBar); var i:integer; begin PB.Min:=0; PB.Max:=500000; for i := PB.Min to PB.Max do PB.Position:=i; end; |
Процедура для всех остальных читающих потоков
1 2 3 4 5 6 7 8 9 10 11 12 13 |
procedure ProgressRead(PB:TProgressBar); var i:integer; begin Semaphore.Acquire; // Начало блокировки кода PB.Min:=form2.ProgressBar.Min; PB.Max:=form2.ProgressBar.Max; PB.Position:=form2.ProgressBar.Position; for i:=PB.Position to PB.Max do PB.Position := i; Semaphore.Release; // Конец блокировки кода end; |
Далее процедуры Execute всех потоков
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
procedure TFirstThread.execute; begin inherited; ProgressWrite(form.ProgressBar1); end; procedure TSecondThread.execute; begin inherited; ProgressRead(form.ProgressBar2); end; { TThirdThread } procedure TThirdThread.execute; begin inherited; ProgressRead(form.ProgressBar3); end; { TFourthThread } procedure TFourthThread.execute; begin inherited; ProgressRead(form.ProgressBar4); end; |
Далее вызов потоков на кнопках
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
procedure TForm.Start1Click(Sender: TObject); begin FirstThread := TFirstThread.Create(false); Firstthread.FreeOnTerminate:=true; end; procedure TForm.Start2Click(Sender: TObject); begin SecondThread := TSecondThread.Create(false); SecondThread.FreeOnTerminate:=true; end; procedure TForm.Start3Click(Sender: TObject); begin ThirdThread:=TThirdThread.Create(false); ThirdThread.FreeOnTerminate:=true; end; procedure TForm.Start4Click(Sender: TObject); begin FourthThread:=TFourthThread.Create(false); Fourththread.FreeOnTerminate:=true; end; |
В принципе это вся программа. Жмем на Start1, далее на любую кнопку из Start1, Start2, Start3. В данном случае мы использовали потоки внутри приложения, но также можно было бы использовать потоки из разных приложений, использующих один и тот же ресурс. Суть семафоров – в управлении “популяцией” потоков, имеющих доступ к разделяемым ресурсам.