Следующим отличием от пошагового программирования является то, что есть общие данные для большого числа процессов. А в некоторых случаях требуется получить индивидуальный доступ до этих данных.
>('x set 0) 0 >(100 for i ('x set (x + 1))) ; возникнет 100 параллельных процессов nil >x 7
Данную возможность можно получить используя замки:
>('x set 0) 0 >('xlock set ('lock newobject)) Lock >(100 for i (xlock progn ('x set (x + 1)))) nil >x 100
Самое важное, чтобы все процессы, которые изменяют общие данные, использовали замок. Дело в том, что замок это просто флажок - знак того, что место занято. А если не обращать внимание на флажки можно сделать всё что угодно.
Сигналы необходимы для общения нескольких процессов. Если процесс ожидает выполнения некоторой задачи, то выполнение циклической проверки очень сильно и максимально нагружает процессоры бесполезным трудом.
При составлении программы сначала устанавливается замок. Под защитой замка проверяется есть-ли необходимость использовать сигналы. Затем используется очень простая функция wait. Эта функция открывает замок и ждёт сигнал от других процессов. После получения сигнала замок снова устанавливается.
При пошаговом (без параллелизма) программировании программы постоянно проверяют некоторые условия. Это усложняет программирование и очень сильно подвержено ошибкам. При использовании сигналов программа получается очень проста и эффективна.
Приведём очень простой пример:
('signal set ('signal newobject)) ('lock set ('lock newobject)) ('s set (10 copy)) (nil parallel ; основная задача (lock progn (cout writeln "жду...") (signal wait lock) (cout writeln "OK")) ; другая задача (nil progn (nil while (s > 0) (lock progn (cout writeln "работаю") (s -= 1))) (cout writeln "посылаю сигнал") (signal send)))
Порядок подачи, ожидания сигнала не имеет значения. Посланные сигналы не пропадают.
В крупных проектах иногда есть необходимость обрабатывать большой источник информации, не модифицируя его. И желательно дать возможность параллельным процессам выполнятся одновременно. Если появилась необходимость модифицировать информацию то нужно чтобы работал один процесс.
Этот сказочный процесс можно сделать усилиями замков и сигналов. Сделаем класс passage который организует весь процесс работы.
(('passage defclass) defvar lockEntrance lockOutlet signalShout guests flagToShout) ('passage defmethod passage () (nil setq lockEntrance ('lock newobject) lockOutlet ('lock newobject) signalShout ('signal newobject) guests (0 copy) flagToShout false)) ('passage defmacro Pass (env &rest body) (lockEntrance progn (lockOutlet progn (guests += 1))) (nil let ((result (env eval `(nil progn ,@body)))) (lockOutlet progn (nil if ((guests -= 1) = 0) (nil when flagToShout ('flagToShout set false) (signalShout send)))) `',result)) ('passage defmacro Lock (env &rest body) (lockEntrance progn (lockOutlet progn (nil when (guests <> 0) ('flagToShout set true) (signalShout wait lockOutlet))) `',(env eval `(nil progn ,@body))))