Управляемый режим блокировки данных (1С: V8: Программисту) — 1C-h
 

Управляемый режим блокировки данных (1С: V8: Программисту)

18

Как использовать управляемый режим блокировки данных

Рассмотрим на примере удаления записей из регистра сведений.
1. В свойствах объекта, для которого используется управляемый режим блокировки данных установить поле «Режим управления блокировкой данных» в значение «Управляемый» (иначе будет сообщение об ошибке «Автоматический режим блокировки недопустим в этой транзакции»). Причем если это делается для документа, то необходимо перевести в этот же режим блокировки все регистры, в которых делает движения этот документ.
2. Пример кода управляемой блокировки регистра при удалении из него записи:

Например:

Процедура ОчиститьЗаПериод()
	Пока Истина Цикл
		НачатьТранзакцию(РежимУправленияБлокировкойДанных.Управляемый);
		Попытка
			текЗапрос = новый Запрос;
			текЗапрос.Текст = 

			"ВЫБРАТЬ
			|	усИсторияОстатковПоПоказателям.Склад,
			|	усИсторияОстатковПоПоказателям.Поклажедатель,
			|	усИсторияОстатковПоПоказателям.Показатель,
			|	усИсторияОстатковПоПоказателям.Период
			|ИЗ
			|	РегистрСведений.усИсторияОстатковПоПоказателям КАК усИсторияОстатковПоПоказателям
			|ГДЕ
			|	усИсторияОстатковПоПоказателям.Период МЕЖДУ &ПериодС И &ПериодПо";

			текЗапрос.УстановитьПараметр("ПериодС", ГраницаОчисткиС);
			текЗапрос.УстановитьПараметр("ПериодПо", ГраницаОчисткиПо);
			тбзЗаписиЖурнала = текЗапрос.Выполнить().Выгрузить();

			// Блокировка данных
			текБлокировка = Новый БлокировкаДанных;
			тбдЖурнал = текБлокировка.Добавить("РегистрСведений.усИсторияОстатковПоПоказателям");
			тбдЖурнал.Режим = РежимБлокировкиДанных.Исключительный;
			Если тбзЗаписиЖурнала.Количество() тогда
				тбдЖурнал.ИсточникДанных = тбзЗаписиЖурнала;
				тбдЖурнал.ИспользоватьИзИсточникаДанных("Склад", "Склад");
				тбдЖурнал.ИспользоватьИзИсточникаДанных("Поклажедатель", "Поклажедатель");
				тбдЖурнал.ИспользоватьИзИсточникаДанных("Показатель", "Показатель");

				// Заблокировать
				текБлокировка.Заблокировать();

				Для каждого текЗаписьЖурнала из тбзЗаписиЖурнала цикл
					текЗапись = РегистрыСведений.усИсторияОстатковПоПоказателям.СоздатьМенеджерЗаписи();
					ЗаполнитьЗначенияСвойств(текЗапись, текЗаписьЖурнала);
					текЗапись.Удалить();
				КонецЦикла;

			КонецЕсли;

			ЗафиксироватьТранзакцию();
			Прервать;

		Исключение // сообщим об ошибке
			стрОписаниеОшибки = ОписаниеОшибки();
			ОтменитьТранзакцию();
			Сообщить("Ошибка блокировки" + ОписаниеОшибки());
			Прервать;
		КонецПопытки;
	КонецЦикла;
КонецПроцедуры // ОчиститьЗаПериод()

18 thoughts on “Управляемый режим блокировки данных (1С: V8: Программисту)

  1. А для чего внешний цикл вокруг попытки, если он в обоих случаях прерывается без продолжения?

  2. Хороший вопрос 🙂
    Если код оставить как есть, то цикл и его прерывание действительно не нужны.
    На самом деле в «Исключении» предполагалась обработка текста исключительной ситуации с целью повторения попыток, если исключение произошло из-за взаимоблокировок. Что-то вроде такого кода:

    		Исключение // Запись информации об ошибке
    			стрОписаниеОшибки = ОписаниеОшибки();
    			ОтменитьТранзакцию();
    			
    			БылаОшибка = Истина; // борьба с зависаниями намертво
    			Если СчетчикПовторов > ДопустимоеКоличествоПовторов + 1 Тогда // не проверяем текст собщения, уже ясно, что конфликт блокировок и надоело ждать
    				Прервать;
    			Иначе
    				Если НЕ ахОшибкаБлокировки(стрОписаниеОшибки, «Модуль приложения, Процедура ОчиститьЗаПериод») Тогда
    					Прервать;
    				КонецЕсли;
    			КонецЕсли;
    		КонецПопытки;
    
  3. При этом текст вызываемой функции с проверкой на взаимоблокировки примерно такой:

    Функция ахОшибкаБлокировки(стрОписаниеОшибки, ДопИнформация = «») Экспорт
    	Если Найти(стрОписаниеОшибки, «HRESULT=80004005») Или Найти(стрОписаниеОшибки, «HRESULT=80040E31») // MS SQL Server 
    		Или Найти(стрОписаниеОшибки, «Превышено максимальное время ожидания предоставления блокировки») Или Найти(стрОписаниеОшибки, «Неустранимый конфликт блокировок») // Управляемые блокировки
    		Тогда
    		Задержка = СлучайноеЧисло(1000);
    		
    		// Пустой Цикл для задержки.
    		Для Индекс = 0 По Задержка Цикл
    			
    		КонецЦикла;
    		
    		// сообщим в журнал регистрации о блокировке
    		ЗаписьЖурналаРегистрации(«->Ошибка блокировки (информация)»,УровеньЖурналаРегистрации.Ошибка,,, «Сообщение из функции ахОшибкаБлокировки (общий модуль усПроведениеДокументов)»+стрОписаниеОшибки+»ДопИнформация:»+ДопИнформация); // 
    		
    		Возврат Истина;
    	Иначе
    		Возврат Ложь; 
    	КонецЕсли;
    КонецФункции
    
  4. Мдя… Посмотрел html-код странички. Тэг «pre» тоже присутствовал в моем коде, но остался только «code». Кстати, форматирование кода сохранилось внутри html и если обернуть тэгами «pre» блок «code», то будет выглядеть правильно.
    И было бы удобно, если бы здесь после поля ввода комментария была кнопочка «Preview Comments», а над полем — набор кнопочек для форматирования (есть такие плагины для WordPress).

  5. Я был неправ по поводу необходимости самостоятельного снятия блокировки после фиксации транзакции.

    Блокировка снимается автоматически:
    «Если этот метод [метод Заблокировать() объекта БлокировкаДанных] выполняется в транзакции (явной или неявной), блокировки устанавливаются, и при окончании транзакции будут сняты автоматически.» (1-361 в 8.1, 1-501 в 8.2)

    Меня ввело в заблуждение указание, что:
    «Блокировки объектов, установленные в течение транзакции, сохраняются при фиксации транзакции и снимаются при откате транзакции.» (1-354 в 8.1)

    Похоже, что это ошибка в документации 8.1, поскольку то же предложение в документации 8.2 выглядит следующим образом:
    «Блокировки объектов, установленные в течение транзакции, снимаются при окончании транзакции, если блокировка устанавливалась без указания идентификатора формы.» (1-494 в 8.2)

  6. Здравствуйте.

    Я новичок в 1С — только что первую книгу прочитал по 8.1. Ни разу еще не кодил.
    Полез в Гугл прояснить для себя несколько вопросов и попал на Вашу страничку.

    Поэтому у меня есть несколько нубских вопросов.

    По поводу функции ахОшибкаБлокировки() три вопроса:

    1. Интересно, что будет написано в английской версии вместо
    «Превышено максимальное время ожидания предоставления блокировки»
    и «Неустранимый конфликт блокировок»?

    2. Вместо spin-блокировки в 1С другой возможности подождать нет?
    Какой-нибудь sleep(1000)?

    3. Зачем тут используется СлучайноеЧисло()?

    По поводу исходного поста, по-моему:

    1. Внешний цикл не нужнен.

    2. Нужно снять блокировки после фиксации транзакции.

    3. Не нужно откатывать всю транзакцию, чтобы повторить получение блокировки.
    Достаточно в цикл проверки и try/except обернуть только инструкцию «текБлокировка.Заблокировать();».
    Можно такую «многопопыточную» блокировку оформить в функцию.

    Примерно так:

    Процедура ОчиститьЗаПериод()
        НачатьТранзакцию( РежимУправленияБлокировкойДанных.Управляемый );
        Попытка
            …
            Если тбзЗаписиЖурнала.Количество() тогда
                …
    
                // Заблокировать 
                БлокировкаМногопопыточная( текБлокировкаДанных );
    
                …
            КонецЕсли;
    
            ЗафиксироватьТранзакцию();
            текБлокировка.Разблокировать();
    
        Исключение
            стрОписаниеОшибки = ОписаниеОшибки();
            ЗаписьЖурналаРегистрации( «->Ошибка (ахтунг)», УровеньЖурналаРегистрации.Ошибка, , ,
                «Сообщение из Процедура ОчиститьЗаПериод(): » + стрОписаниеОшибки );
            Сообщить( «Ошибка: «, стрОписаниеОшибки );
        КонецПопытки;
    КонецПроцедуры
    
    Функция БлокировкаМногопопыточная( текБлокировкаДанных, ДопустимоеКоличествоПовторов = 30, ДопИнформация = «» ) Экспорт
        Перем ОсталосьПовторов = ДопустимоеКоличествоПовторов;
        Пока ОсталосьПовторов >; 0 Цикл
            ОсталосьПовторов = ОсталосьПовторов — 1;
            Попытка
                текБлокировка.Заблокировать();
                Прервать;
            Исключение
                стрОписаниеОшибки = ОписаниеОшибки();
                Если НЕ ахОшибкаБлокировки( стрОписаниеОшибки, ДопИнформация ) Тогда
                    ВызватьИсключение;
                КонецЕсли;
            КонецПопытки;
        КонецЦикла;
        Если НЕ текБлокировка.Заблокирован() Тогда
            ВызватьИсключение «Превышено допустимое количество повторов получения блокировки.»;
        КонецЕсли;
    КонецФункции
    

    Код в 1С не проверялся. Если я в чем-то ошибся, поправьте меня.

  7. Интересно, что будет написано в английской версии вместо
    «Превышено максимальное время ожидания предоставления блокировки»
    и «Неустранимый конфликт блокировок»?

    Написано будет, конечно, не на русском. Чтобы посмотреть, что именно будет написано, нужно поэкспериментировать в английской версии с блокировками и прочитать то, что предприятие выдаст в ответ.

    2. Вместо spin-блокировки в 1С другой возможности подождать нет?
    Какой-нибудь sleep(1000)?

    3. Зачем тут используется СлучайноеЧисло()?

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

    Кстати, «опыт эксплуатации» этого кода показывает, что такой модуль спасает ситуацию не всегда. Возможно, стоит поэкспериментировать дальше, и задавать интервал задержки побольше — больше вероятность, что объекты выйдут из цикла «дальше» друг от друга.

    Или придумать другую, более надежную схему.

    Кстати, спасибо за Ваши примеры. Посмотрим.

  8. Я был неправ по поводу необходимости самостоятельного снятия блокировки после фиксации транзакции.

    Блокировка снимается автоматически:
    «Если этот метод [метод Заблокировать() объекта БлокировкаДанных] выполняется в транзакции (явной или неявной), блокировки устанавливаются, и при окончании транзакции будут сняты автоматически.» (1-361 в 8.1, 1-501 в 8.2)

    Меня ввело в заблуждение указание, что:
    «Блокировки объектов, установленные в течение транзакции, сохраняются при фиксации транзакции и снимаются при откате транзакции.» (1-354 в 8.1)

    Похоже, что это ошибка в документации 8.1, поскольку то же предложение в документации 8.2 выглядит следующим образом:
    «Блокировки объектов, установленные в течение транзакции, снимаются при окончании транзакции, если блокировка устанавливалась без указания идентификатора формы.» (1-494 в 8.2)

    Не совсем так. В книге все верно. Речь в цитируемом Вами месте (где Вы усмотрели ошибку) идет о пессимистической объектной блокировке, осуществляемой командой вида

    ТекОбъект.Заблокировать();
    

    Где ТекОбъект – это объект, например, справочника «Номенклатура», предварительно полученный, например, таким выражением:

    Справочники.Номенклатура.НайтиПоКоду(«000001»).ПолучитьОбъект();
    

    А в данной статье приведена в пример функция, в которой используется управляемая транзакционная блокировка. Подробнее см. Уровни изоляции транзакций в MS SQL Server и управляемые блокировки 1С. Объектные блокировки, пессимистические и оптимистические. Взаимоблокировки. (SQL Server и 1С)

  9. Огромное спасибо за разъяснения по поводу блокировок объектов. У меня теперь в мозгу порядок наступил.

    Нашел еще две главы хорошего описания различий в блокировках между версиями 8.1 и 8.2 в книге
    М.Г.Радченко «1С:Предприятие 8.2. Коротко о главном. Новые возможности версии 8.2.»:
    глава «Управляемые блокировки данных используются стандартно» (стр.298)
    и глава «Пессимистическая блокировка» (стр.378).

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

    Поэтому сразу скажу, что у меня такая система:
    1С 8.2.12.80 на PostgreSQL 8.3.3-2.1C.
    (комп = Pentium Core 2 Duo 3.0 GHz, 2 Gb RAM, WinXP).

    Еще раз спасибо за разъяснения про блокировки.

    А теперь хочу снова обратить Ваше внимание на тот «Пустой Цикл для задержки».
    Вы не вспомните, когда Вы разруливали конфликты этим кодом,
    какие были параметры системы (1С, процессор, версия Windows)?
    И какая была загруженность процессора, в процентах?
    Это нужно для того, чтобы понять, почему этот код все-таки помогал разрулить ситуацию.

  10. 1C 8.1, 15 релиз платформы, SQL-ная
    64-разрядная Windows на сервере
    загруженность процессора в % сказать не могу

    Теперь подробнее о том, как происходит взаимная блокировка, при которой этот код помогает. Представьте, что два соединения одновременно в попытке читают одни и те же данные. Происходит ошибка. Транзакции обоих соединений отменяются. Оба идут в функцию «ахОшибкаБлокировки» одновременно. В «Описаниях ошибок» обоих процессов есть слова «Превышено максимальное время ожидания предоставления блокировки» и они оба попадают в этот цикл. Наша задача отпустить их из функции в разное время, т.к. выйдя из нее они снова будут пытаться прочитать те же данные. Представьте, что первое соединение выходит из функции раньше, оно читает данные на этот раз успешно, т.к. второе все еще «крутится» в цикле задержки. Первое соединение данные получило, транзакция зафиксирована, второе выходит из цикла и идет читать данные. Все. Проблема разрешена.

    Однако бывает и так, что три соединения одновременно пытаются обратиться к одним и тем же данным. В большинстве случаев эта ситуация превращается в «мертвое зависание».
    Чтобы решить и эту ситуацию, в наружный цикл с управляемой блокировкой добавляется счетчик попыток. По превышению счетчика, соединение, у которого первым оказалось превышение счетчика, выводится из цикла, чтобы два остальных процесса завершились нормально. Если требуется, или что-то осталось непонятным, могу привести полный текст этих процедур и функций.

    1. > соединение, у которого первым оказалось превышение счетчика, выводится из цикла

      Поясним. При выводе из цикла этого третьего соединения, пользователю можно послать соответсвующее сообщение и просьбу запустить процесс заново через несколько секунд. За это время те два соединения успеют разрешить «спор» о доступе.

      Если процесс выполняется регламентно (регламентный запуск без участия пользователя), то можно дать ему высший приоритет, постаравшись добиться того, чтобы вышел из цикла «соперничаюший» процесс, запущенный пользователем. Но тут тогда тоже нужно предусмотреть вариант, когда все три процесса окажутся регламентными, чтобы они не уперлись друг в друга (т.е. реще раз «раздать приоритеты»)

  11. Попробую объяснить свою точку зрения на то, почему в вашем случае 1000 итераций пустого цикла помогает разрешить ситуацию.

    Рассмотрим такой сценарий:
    Пусть у нас есть таблица с двумя записями {А, Б}. Цифрой буду отмечать номер клиента, блокировавшего данные:

    {А, Б}
    1. Клиент1 запросил блокировку и заблокировал запись A.
    {1, Б}
    2. Клиент2 запросил блокировку и заблокировал запись Б.
    {1, 2}
    3. Клиент3 запросил блокировку на А и Б, и теперь ждет.
    {1, 2}
    4. Клиент1 закончил обработку и снял блокировку с A. Клиент3 ждет.
    {A, 2}
    5. Клиент4 запросил блокировку и заблокировал запись A. Клиент3 ждет.
    {4, 2}
    6. Клиент2 закончил обработку и снял блокировку с Б. Клиент3 ждет.
    {4, Б}
    7. Клиент5 запросил блокировку и заблокировал запись Б. Клиент3 ждет.
    {4, 5}
    и т.д.

    (Замечу, что Клиент3 не просто сидит и ждет — он постоянно перепроверяет индексы блокировок и увеличивает нагрузку на базу данных.)

    Получается, что Клиент3 так и не получил своей блокировки на обе записи сразу, хотя по-очереди они были свободны.
    Задача решится, если, например, шаги 5. и 6. поменять местами. Тогда Клиент3 сможет заблокировать обе записи
    и выполнить свою работу.

    Добавление пустого цикла должно бы «перемешивает» очередность блокировок и с некоторой вероятностью время от времени выдавать
    годный вариант. Но в сложном случае может и не выдать ничего подходящего.

    1) Рассмотрим первый сценарий, где ваш цикл мог бы сработать.

    Насколько я понял, для каждого сеанса на сервере создается отдельный поток в рамках процесса rphost.exe
    У меня запущен только один такой процесс, и все сеансы работают в его потоках.
    В нашем случае можно считать, что разруливание процессорного времени между потоками сводится просто к
    очереди потоков, и каждому потоку для работы выделяется на свободном процессоре квант процессорного времени.
    На моем компьютере квант времени примерно равен 0.030 секунды, на вашем сервере — примерно 0.180 секунды.

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

    Когда я увидел, что количество итераций в вашем пустом цикле всего 1000, то сначала посмеялся, потом задумался,
    а когда провел тестирование, то плакать захотелось, потому что скорость итераций пустого цикла:
    — Сервер 1С в нормальном режиме — 100 млн. итераций за 55 секунд
    — Сервер 1С с опцией «-debug» — 100 млн. итераций за 217 секунд
    Это примерно в 60 (!) раз медленнее, чем в VBA.
    А в C# (и вообще .Net) — еще в 3 раза быстрее, чем в VBA.
    А пишут, что 1С компилирует модули.
    А вот ведь…

    Однако, вернемся к нашим клиентам. Если пересчитать это на квант, то получим, что на моей машине
    квант может содержать примерно 55000 итераций (на вашей — порядка 330000 итераций).

    Очевидно, что количества в 1000 не достаточно, чтобы гарантированно выскочить из кванта.
    Поэтому этот сценарий скорее всего не срабатывает.

    2) По всей вероятности, в перемешивании сеансов главную роль играет многопроцессорность.
    Ведь при примерно одновременном начале исполнения двух потоков такая легкая случайная сдвижка
    будет достаточно влиять на пару сеансов (если два ядра).

    В этом сценарии, если нужно перемешивать три сеанса, то нужно уже три ядра, и т.д.

    Можно выделить недостатки обоих сценариев вообще и использования пустого цикла в частности:

    Во-первых, увеличенная нагрузка на процессор при использовании пустого цикла (особенно по первому сценарию).
    Во-вторых, увеличенная нагрузка на базу данных из-за постоянных переблокировок и проверки индексов блокировок.
    Во-третьих, вероятность не разрулить конфликт.

    Начну с первого — вместо пустого цикла («spin-блокировки») нужно использовать функцию для приостановки потока.

    В 1С, к сожалению, такой функции нет.
    Системной функцией Windows для приостановки потока является sleep из kernel32.dll.
    Получить доступ к этой функции можно через COM.
    В сети находил несколько вариантов (dynwrap, запуск через WScript.Shell и прочее).
    Для себя выбрал «COM объект реализующий метод Sleep» infostart_ru/public/16230/
    (Там есть и 32- и 64-битная версии. Если будете использовать 32-битную версию на 64-битной машине,
    то обратите внимание на замечание, о том, что нужно использовать C:WINDOWSSYSWOW64REGSVR32.EXE
    на 1cpp_ru/forum/YaBB.pl?num=1234187982/9 ).

    Почему все-таки нужно отказываться от spin-блокировок?

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

    Для тестирования, запишем бесконечный цикл (Пока Истина Цикл КонецЦикла;)
    в любом серверном коде и запустим на выполнение.

    Проверка покажет, что один ожидающий поток занимает 50% двухядерного процессора, а два — 100%.
    При 5-ти одновременно ожидающих клиентах интерфейс Windows уже заметно тормозит
    (окно диспетчера задач не удается переместить по экрану без рывков).
    При запуске 100 клиентов тормоза становятся такими, что невозможно открыть список сессий в консоли администрирования
    сервера 1С. Пришлось прибить процесс rphost.exe и перезагрузить сервер 1С.

    С другой стороны, добавим в цикл вызов Sleep(1). Результаты тестирования следующие:

    Удалось создать 1542 сеанса, из них:
    1 сеанс «Конфигуратор»
    2 сеанса «Тонкий клиент»
    1539 сеансов «Фоновое задание» (выполняли бесконечный цикл вызовов Sleep(1) )
    процесс rphost.exe захватил 364 Мегабайт

    Загруженность процессора (perfmon.msc): средняя — 12%, максимальная — 29%.
    При этом я вполне комфортно работал — доки читал, в интернете лазил.

    Таким образом, используя Sleep() вместо spin-блокировки, получаем уменьшение загруженности
    процессора в ( 1539 / 2 ) * ( 100 / 12 ) = 6412,5 раз.

    Мне такой результат понравился. А Вам?

    И, наконец, решение проблемы взаимо-блокировок: нужно упорядочить запросы на блокировки данных.
    То есть нужно выстроить их в очередь так, чтобы пока Клиент3 не выставил свои блокировки, Клиент4
    не мог запросить блокировки для своей маленькой транзакции.
    Такое решение полностью разрешит все конфликты и уменьшит нагрузку на базу данных.

    А теперь вопрос: есть ли в 1С какой нибудь вариант организации очереди?

  12. Спасибо за интереснейшие комментарии и обстоятельное изложение. Попробуем этот вариант.

    По поводу организации очереди. У нас была задача не запускать фоновое задание до тех пор, пока это же фоновое задание выполняется другим соединением. Для организации этого использовали довольно примитивный способ: создали константу типа «Процесс уже идет», и при каждой попытке запуска этого же фонового задания очередным соединением проверяется эта константа. Это конечно, не очередь в чистом виде, а так, имитация команды вида «не мешай, пока я не закончил».

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

  13. Учусь на ошибках.

    Во-первых, хочу отметить, что в посте 4 я был неправ по всем трем предположениям «по-моему».

    По поводу «2. Нужно снять блокировки после фиксации транзакции.» — уже разобрались в постах 6. и 7.

    А вот про «3. Не нужно откатывать всю транзакцию, чтобы повторить получение блокировки.» никто меня не проправил.
    Проверка показала, что если блокировка не удалась, то вся транзакция помечается как ошибочная и продолжать
    работу в этой транзакции нельзя — нужно ее откатить.
    Соответственно, утверждение «1. Внешний цикл не нужнен.» также неверно.
    Таким образом, код приведенный в посте 4., не только содержит синтаксические ошибки, но и не верен в логике.

    Во-вторых, упомянутый мною «COM объект реализующий метод Sleep» infostart_ru/public/16230/
    на данный момент является STA-компонентой, а не MTA.
    Это означает, что при вызове функции Sleep() из этой компоненты блокируются все потоки, которые
    обращаются к этой и другим STA-компонентам. Получается, что засыпают сразу все такие потоки.
    Это очень плохо.

    Для того, чтобы превратить эту компоненту в MTA, нужно в ветке реестра
    [HKEY_CLASSES_ROOTCLSID{E13F1EF6-D43C-4BC8-8571-F2D7F0190DA4}InprocServer32]
    исправить значение
    «ThreadingModel»=»Apartment»
    заменить на
    «ThreadingModel»=»Free», и перезапустить сервер 1С.
    Поскольку эта компонента простая (все сводится к вызову ::Sleep(dwMillisecons)),
    то такого исправления вполне достаточно (перекомпиляция не обязательна).

    Проверить можно так:
    1. Запустить два сеанса.
    2. В первом вызвать Sleep(120000) в процедуре, помеченной «&НаСервере».
    3. Из второго сразу вызвать Sleep(5000) в процедуре, помеченной «&НаСервере».

    Если MTA, то второй сеанс будет ждать 5 секунд.
    Если STA, то второй сеанс будет ждать 125 секунд: сначала дождется доступа к занятому STA-потоку,
    а затем сам займет его на 5 секунд.

  14. Проверка показала, что если блокировка не удалась, то вся транзакция помечается как ошибочная и продолжать
    работу в этой транзакции нельзя – нужно ее откатить.
    Соответственно, утверждение “1. Внешний цикл не нужнен.” также неверно.

    Если рассматривать первый модуль таким, как он приведен изначально (в самом первом посте), не подразумевая другую обработку «Исключения», то внешний цикл там действительно лишний – ведь ни в случае фиксации, ни в случае отката транзакции, цикл не повторяется (см. на «Прервать» в «Исключении»).

  15. По поводу организации очереди блокировок.

    В зависимости от ситуации, можно рассмотреть применение, например, таких трех вариантов:

    1. Очередь на блокировке таблицы всеми транзакциями.

    Хотя с первого взгляда, блокирование всех транзакций на все время выполнения одной, выглядит устрашающе, но если выполнение одной транзакции достаточно быстро, то очередь вполне нормально продвигается.
    И при этом отсутствие конфликтов блокировок позволяет улучшить пропускную способность при очень большой нагрузке: с одной стороны за счет последовательного выполнения транзакций, а с другой — из-за того, что установка блокировки на таблицу быстрее, чем установка блокировки даже одной строки.

    А сервер простаивать не будет — он будет последовательно ставить блокировки, читать-писать, снимать блокировки, да к тому же параллельно работать с запросами других частей конфигурации.

    2. Очередь на блокировке таблицы крупными транзакциями (т.е. очередь только для крупных транзакций).

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

    3. Очередь на блокировке с помощью объектов, не связанных с базой данных. Для сервера 1С на одном компьютере подходят, например, объекты ядра Windows (критические секции, мьютексы, события с автосбросом и т.д.).

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

    Для себя я реализовал очередь блокировок на критической секции.

    Очередь основана на том, что при вызове EnterCriticalSection() поток либо получает единоличный доступ к критической секции, либо ставится планировщиком потоков в FIFO-очередь без таймаута.
    Поток, владеющий критической секцией, может освободить ее вызовом LeaveCriticalSection().
    Поддерживается вложенность вызовов, т.е. если вызвать EnterCriticalSection() два раза подряд, то не произойдет самоблокировки, но увеличится счетчик, поэтому нужно также дважды вызвать LeaveCriticalSection() для освобождения критической секции.

    Для реализации этой очереди необходим COM-объект, запускаемый в MTA (Multi-Thread Apartment) и предоставляющий критическую секцию и методы EnterCriticalSection() и LeaveCriticalSection().
    Критическая секция должна быть глобальной для процесса rphost.exe, то есть единой для всех потоков сессий, обрабатываемых в этом процессе.

    Также, стоит добавить в COM-объект внутренний счетчик глубины входов в критическую секцию для потока, а в деструктор добавить выход из критической секции нужное количество раз, если счетчик окажется не нулевым.
    Это нужно на случай, если поток (программист 1С) по каким-то причинам забудет вызвать LeaveCriticalSection() после EnterCriticalSection(). Иначе снять занятость критической секции можно будет только перезапуском сервера 1С.

    В такую очередь у меня ставились все транзакции — и крупные, и мелкие.

    Плюсы очереди блокировок на критической секции:

    1. Скорость работы самих критических секций намного выше, чем тех же блокировок таблицы.
    В общем-то, скорость такой очереди упирается только в скорость установки блокировок нужных данных.

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

    3. Если имеется вложенное обращение к очереди, то работающий поток сразу «пройдет к кассе» исключая деадлок, поскольку критические секции поддерживают вложенность вызовов.

    Минусы указанной реализации:

    1. Критические секции можно использовать только в рамках одного процесса rphost.exe.

    Если у вас несколько процессов rphost.exe на одной машине, то вместо критических секций можно, например, использовать именованный мьютекс.

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

    Я предполагаю использование «именнованных» критических секций, когда в COM-объект передается имя, и если такого имени нет в его списке, то создается новая критическая секция, связанная с этим именем.
    Имя должно быть уникально в рамках сервера 1С, например, «имя-базы/имя-в-конфигурации».

    3. Использовать критические секции нужно очень аккуратно:
    — не допускать вызова EnterCriticalSection() без соответствующего ему вызова LeaveCriticalSection(), и наоборот, вызова LeaveCriticalSection() без EnterCriticalSection().
    — не создавать деадлоков критических секций (если будет использоваться несколько критических секций).

    4. Нет параллельности обработки независимых транзакций, которые идут в очереди не последовательно.

    4.1. Пример:

    Если есть работающая Транзакция1, блокирующая записи {А, Б},
    в вершине очереди стоит Транзакция2, пытающаяся блокировать запись {А},
    а затем в очереди идет Транзакция3, которая будет работать с записью {С}.

    Транзакция2 будет ожидать снятия блокировки на запись {А} из-за работы первой транзакцией.
    А Транзакция3, которая могла бы быть обработана, будет ожидать в очереди, поскольку
    Транзакция2 еще не выставила свои блокировки и не освободила очередь.

    4.2. Пример:

    Если крупная транзакция занята тем, что ставит блокировку, то ее ждут все остальные в очереди.
    При этом, если в диапазоне блокировок этой транзакции имеется блокировка строки, над которой какая-то мелкая транзакция будет работать еще долго, то и эта крупная и все остальные в очереди будут ожидать до таймаута блокировки на SQL-сервере.

    Решение этих проблем заключается в разработке более сложной COM-компоненты и включает следующие дополнения: передача в COM-объект информации о диапазоне блокировки и максимальном времени работы транзакции, начиная с момента после установления блокировок; ведение в COM-объекте статистики среднего времени работы похожих транзакций; реализация методов для сигнализации о завершении работы транзакции; реализация логики принятия решения о проталкивании одних транзакций вперед и выкидывании из очереди «зависающих» транзакций (фактически, дублирование функциональности блокировок SQL-сервера, только с нулевым таймаутом и логикой, соответствующей, конкретному случаю). Потребуются также средства административного управления информацией и логикой COM-объекта (просмотра и правки накопленной статистики и списков работающих транзакций, проталкивания определенных транзакций и т.п.).

    И конечно, прежде чем воспользоваться каким-то вариантом организации очереди блокировок на рабочем сервере, желательно провести хоть какое-то тестирование (собрать образцы запросов с временными метками, сделать тестовую базу на тестовом сервере и прогнать тесты без очереди блокировок и с разными вариантами очереди блокировок).

  16. Я подписался на RSS ленту, но сообщения почему-то в виде каких-то иероглифов 🙁 Как это исправить?

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Next Post

Установка защиты модуля ТСД (1С: Логистика - управление складом Axelot: Программисту и Пользователю)

Вс Июл 18 , 2010
Вопрос Как временно поменять модуль защиты с Предприятия на ТСД, если защита установлена на разных серверах? Ответ Для того, чтобы можно было работать с модулем ТСД (модулем работы с терминалом сбора данных), нужно в меню Сервис-Параметры заменить сервер на тот, на котором установлен ключ, предварительно создав резервную копию файла C:Program […]