Менеджер контекста

Одиночный оператор захвата ссылки и синхронизации доступа к объекту ‘*’ или ‘*(…)’ выполняется только для одной переменной при использовании в одном действии. Но захват ссылки и объекта синхронизации, это относительно медленная операция и выполнять её для каждого действия над переменной не рационально.

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

    **( val1 = *ref1, val2 = *ref2, __timeout__ = 10000 ){
        ...
    };

    **( ... ){
        ...
    } , [...] {
        # Ветка else при ошибке захвата ссылки
        # Информация об ошибке в переменной $^
        ...
    };

Или таже самая запись с использованием макросов DSL:

    @with( val1 = *ref1, val2 = *ref2, __timeout__ = 10000 ){
        ...
    };

    @with( ... ){
        ...
    } @else {
        # Ветка else при ошибке захвата ссылки
        # Информация об ошибке в переменной $^
        ...
    };

Захват легких ссылок совместного доступа

При захвате обычной легкой ссылки всегда сравнивается идентификатор текущего потока с идентифкатором потока - владельца объекта. Использование обычных легких ссылок допускается только в рамках одного потока - владельца объекта.

Чтобы можно было использовать ссылки на легкие объекты в других потоках, для них требуется создать легкие ссылки для совместного доступа ("&?").

Захват легких ссылок для совместного доступа возможен только с помощью оператора группового захвата, при условии, что как миниму одна из первых ссылок будет тяжелой. т.е. с реализованным объектом синхронизации.

Пример реализации критической секции с помощью оператора группового захвата @with и статической переменной @::sync в качестве флага блокировки доступа:

    # Функция для вызова в разных потоках
    show( &? win1, &? win2) := {

        && @::sync := @false; # Global (static) synchronization object
        @with (*sync, w1 = *win1, w2 = *win2){
            ... # Критическая  секция
        }
    }

Расширенный синтаксис определения ссылок

При определении переменной, у которой допускается создание ссылок и синхронизация доступа, можно указать дополнительные параметры работы. Это делается с помощью указания системных параметров в типе разрешенных ссылок при определении объекта.

Условный пример:

    
    && sync; # Объект синхронизации
    &&(__timeout__=1000) sync_other; # Объект синхронизации c таймаутом на блокировку

    with(*sync_other) { # Блокировка с таймаутом
        ... 
    } 


    # Переменная value с конкретным объектом синхроинзации
    &?(sync) value := 123;

    with(*sync, lock = *value) { # ОК
        ... 
    } 

    with(*sync_other, val = *value) { # Ошибка компиляции 
        # для синхронизации должен использоваться sync
        ... 
    } 

Автоматическое освобождение ресурсов

Менеджер контекста, кроме захвата слабых ссылок в локальные переменные и блокировки доступа, одновременно с этим позволяет выделять и освобождать ресурсы, автоматически вызывая декструктор локального объекта при выходе из блока кода оператора.

Освобождение ресурсов (вызов деструктора у объекта) происходит до попадания в ветку else, если она присутствует.

Самый популярный пример использования менеджера контекста - связанные операции открытия, чтения/записи и закрытия файла в одном блоке кода.

Например:

    # Фрагмент определения класса File
    File(name, mode) := :Class() {

        .file ::= fopen($name, $mode); # Handle file

        ~File() := { # Destructor of class
            @if(@this.file){
                fclose(@this.file);
                @this.file := _;
            }
        }
        ...
    };

    @with( file = :File('some_file', 'w') ) {
        file.write('Oppa!');
    };

Код выше открывает файл, записывает в него данные и закрывает файл при выходе из блока кода.

Этот код эквивалентен следующему на Python:

    with open('some_file', 'w') as file:
        file.write('Oppa!')

    # Или без менеджера контекста
    file = open('some_file', 'w')
    try:
        file.write('Oppa!')
    finally:
        file.close()

Более сложный пример чтение файла с одновремнным захватом ссылки на буфер и обработкой ошибок:

    @with( file = :File('some_file', 'r'), buffer = *ref_buffer ) {
        file.read(buffer);
    } @else {
        @match(@latter){
            @case( :ErrorLock ) { /* Ошибка захвата ссылки ref_buffer */ };
            @case( :ErrorOpenFile ) { /* Ошибка открытия для записи файла some_file */ };
            @case( :ErrorReadWrite ) { /* Ошибка чтения-записи в файл some_file */ };
            @default @forward; /* Остальные ошибки пробросить дальше */
        };
    };

Использование менеджера контекста - это гарантия закрытия файла и освобождения буфера вне зависимости от того, как будет завершён вложенный код. Распространенный паттерн использования контекстных менеджеров - блокирование и разблокирование ресурсов при многопоточном доступе к объетам, а также закрытие открытых файлов или освобождение захваченных ресурсов.


    printf(format:FmtChar, ...):Int32 := %printf...;

    && holder: Integer[] = [,]; # Empty tensor

    Runnable(cnt:Integer) := {
        $i := 1;
        @while( $i <= $cnt ) {
            *holder []= $i;  # Lock and add new value
            $i += 1;
            @if( $i % ($cnt // 10) == 0) {
                @with(*holder){ # Lock only
                    printf(".");
                }
            }
        }
    }

    $count = 10000;

    thread1 = :Thread(Runnable, $count);
    thread2 = :Thread(Runnable, $count);
    thread3 = :Thread(Runnable, $count);
    thread4 = :Thread(Runnable, $count);
    thread5 = :Thread(Runnable, $count);

    thread1.start();
    thread2.start();
    thread3.start();
    thread4.start();
    thread5.start();

    thread1.join();
    thread2.join();
    thread3.join();
    thread4.join();
    thread5.join();

    @assert(*holder.size() == 5*$count);
    printf("Complete %d!\n", *holder.size());

Вывод:

    >..................................................
    >Complete 50000!
    >_