Идея дня
- Критические секции: запись документа, пересчёт остатков, обмен с внешними системами.
- Стратегии:
- Пессимистическая — берём эксклюзивную блокировку, делаем работу, снимаем.
- Логическая (прикладная) — ведём учёт «занятых ключей» в регистре/таблице с TTL.
- Практика: ретраи при конфликте, ограничение времени (TTL),
обязательный
Наконецдля освобождения.
Если в вашей сборке A1sLocks уже есть готовые процедуры (например,
TryAcquire/Release), используйте их. Ниже — эталонные приёмы, которые можно
положить в свой модуль.
Логическая блокировка по имени (через регистр сведений)
Создайте простой регистр сведений БлокировкиДанных (измерения: Ключ
строка; ресурсы: Владелец строка, Истекает дата). На
Ключ поставьте уникальный индекс.
Функция _СеансID() Экспорт
Возврат A1sS.AsString(ТекущийПользователь()); // при желании добавьте хост/UUID
КонецФункции
Функция TryAcquireNamedLock(Ключ, TTLСек = 60) Экспорт
Если A1sO.Empty(Ключ) Тогда Возврат Ложь; КонецЕсли;
НачатьТранзакцию();
Попытка
// 1) Снимем просроченные блоки по ключу
Запрос = Новый Запрос("
|ВЫБРАТЬ
| Бл.Ссылка
|ИЗ РегистрСведений.БлокировкиДанных КАК Бл
|ГДЕ Бл.Ключ = &Ключ И Бл.Истекает < &Сейчас
");
Таб = A1sQ.Unload(Запрос.ТекстЗапроса, Ключ, ТекущаяДата()); // позиционные параметры
Для каждого Стр Из Таб Цикл
Набор = РегистрСведений.БлокировкиДанных.СоздатьНаборЗаписей();
Набор.Отбор.Ссылка.Установить(Стр.Ссылка);
Набор.Прочитать();
Набор.Записи.Очистить(); // удалим запись
Набор.Записать();
КонецЦикла;
// 2) Попробуем создать новую запись-блокировку
Набор2 = РегистрСведений.БлокировкиДанных.СоздатьНаборЗаписей();
Зап = Набор2.Записи.Добавить();
Зап.Ключ = A1sS.AsString(Ключ);
Зап.Владелец = _СеансID();
Зап.Истекает = ДобавитьСекунды(ТекущаяДата(), TTLСек);
Набор2.Записать(); // упадёт на уникальном индексе, если уже занято
ЗафиксироватьТранзакцию();
Возврат Истина;
Исключение
ОтменитьТранзакцию();
Возврат Ложь; // не удалось захватить
КонецПопытки;
КонецФункции
Процедура ReleaseNamedLock(Ключ) Экспорт
Если A1sO.Empty(Ключ) Тогда Возврат; КонецЕсли;
НачатьТранзакцию();
Попытка
Запрос = Новый Запрос("
|ВЫБРАТЬ
| Бл.Ссылка
|ИЗ РегистрСведений.БлокировкиДанных КАК Бл
|ГДЕ Бл.Ключ = &Ключ
");
Таб = A1sQ.Unload(Запрос.ТекстЗапроса, Ключ);
Для каждого Стр Из Таб Цикл
Набор = РегистрСведений.БлокировкиДанных.СоздатьНаборЗаписей();
Набор.Отбор.Ссылка.Установить(Стр.Ссылка);
Набор.Прочитать();
Набор.Записи.Очистить();
Набор.Записать();
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
// Логируем, но не роняем общий поток
A1sLog.Warn("Day28", "ReleaseNamedLock: ошибка освобождения", Новый Структура("Ключ", Ключ));
КонецПопытки;
КонецПроцедуры
TTL защищает от «вечных» блоков (например, если сессия умерла). При каждом длинном действии
можно «продлевать» блок, обновляя Истекает.
Шаблон использования с ретраями
Функция WithNamedLockDo(Ключ, ПроцедурныйКод) Экспорт
// В 1С нельзя передать процедуру как значение ⇒ используйте этот шаблон вручную:
Возврат Неопределено;
КонецФункции
Процедура ИмпортКаталога() Экспорт
ЛокКлюч = "import:catalog";
МаксПопыток = 10; ПаузаСек = 0.3;
Для П = 1 По МаксПопыток Цикл
Если TryAcquireNamedLock(ЛокКлюч, 120) Тогда
Попытка
A1sLog.Info("Day28", "Импорт стартовал (lock ok)");
// ... критическая секция: читаем файлы, пишем объекты ...
Наконец
ReleaseNamedLock(ЛокКлюч);
A1sLog.Info("Day28", "Импорт завершён (unlock)");
КонецПопытки;
Возврат;
Иначе
Пауза(ПаузаСек);
КонецЕсли;
КонецЦикла;
A1sLog.Warn("Day28", "Импорт пропущен: не удалось взять блокировку");
КонецПроцедуры
Пессимистический подход: запись с ретраями
Когда блокировку явно взять нельзя/не нужно — делаем «мягкий» ретрай при записи.
Функция SafeWrite(Объект, МаксПопыток = 5, ПаузаСек = 0.2) Экспорт
Для П = 1 По МаксПопыток Цикл
Попытка
НачатьТранзакцию();
Объект.Записать();
ЗафиксироватьТранзакцию();
Возврат Истина;
Исключение
ОтменитьТранзакцию();
Пауза(ПаузаСек);
КонецПопытки;
КонецЦикла;
Возврат Ложь;
КонецФункции
// Использование
Процедура ОбновитьЦену(Ссылка, НоваяЦена) Экспорт
Если A1sO.Empty(Ссылка) Тогда Возврат; КонецЕсли;
Об = Ссылка.ПолучитьОбъект();
Об.Цена = A1sO.RoundNum(A1sO.NumOrZero(НоваяЦена), 2);
Если НЕ SafeWrite(Об) Тогда
A1sLog.Error("Day28", "Не удалось записать", Новый Структура("Ссылка,Цена", Ссылка, НоваяЦена));
КонецЕсли;
КонецПроцедуры
Чек-лист безопасной работы
- □ Название блокировки стабильно и уникально по сценарию
(
import:catalog,doc:<id>и т.п.). - □ Есть TTL и чистка просроченных записей.
- □ Всегда освобождаем блок в
Наконец. - □ Для записи без логических блоков — ретрай с ограничением попыток и паузой.
- □ Логируем «взял/не взял/освободил», чтобы легко разбирать затыки.
Практика (15–30 минут)
- Создайте регистр сведений БлокировкиДанных и включите уникальный индекс по полю Ключ.
- Реализуйте
TryAcquireNamedLock,ReleaseNamedLockи примените в одном критическом сценарии. - Добавьте ретрай при записи объекта через
SafeWriteи проверьте поведение при конкуренции. - В отчёте/логе выведите статистику: сколько раз блок брался/не брался, среднее время ожидания.
Итоги курса
Поздравляем! За 28 дней вы собрали рабочий «набор инструментов» A1sCode: быстрые запросы (A1sQ), надёжные утилиты объектов/строк (A1sO/A1sS), логирование (A1sLog), сериализацию (A1sJ/A1sX) и практики работы с массивами (A1sArrays). Сегодня вы дополнили это безопасной конкурирующей записью (A1sLocks). Удачи в проектах!
Примечание
Код и тексты на этой странице сгенерированы ИИ. Возможны неточности и ошибки. Перед использованием проверяйте и адаптируйте примеры под вашу версию платформы 1С и сборку библиотеки A1sCode.