Mutex для Zennoposter. Как обезопасить любые критические действия в многопотоке.

LexxWork

Client
Регистрация
31.10.2013
Сообщения
1 190
Реакции
792
Баллы
113
Привет всем. В этой статье я расскажу о возможности защиты критически важных моментов любого шаблона при работе в многопоточном режиме.
О чем это он? Что это значит? Если на ум приходят такие вопросы, значит вам это либо не нужно либо еще рано.
Баблорубам - даже не заглядывайте сюда.
Данная информация рассчитана на более искушенного пользователя программы а так же для тех, кому действительно интересно узнать что-то реально новое, уникальное, необычное.

Предыстория.
Все пользователи программы рано или поздно сталкиваются с удивительными непонятными ошибками во время работы шаблонов в многопоточном режиме. То файл не так пишется, данные какие-то кривые, ошибки какие-то странные вылазят. Постоянно на форуме устраивают разбор полетов по поводу многопотока.
Логическим завершением таких полетов становится понимание (либо непонимание) того как нужно поступать чтобы не было конфликтов в совместно используемых ресурсах.
Я тут еще немножко нудотины попишу - перечислю проблемные места:
-файлы. Зеннопостер предлагает несколько вариантов. Это список, таблица и просто файл.
-эмуляторы. Можно спокойно пользоваться мышкой и клавой.
Все они, судить не мне, работают в многопотоке достаточно стабильно.
И все. На этом проблемные места заканчиваются, но так ли это?

Проблемное место.
Когда-то давно я писал для себя собственный эмулятор мыши и не думал делать свою библиотеку потоконезависимой. Но чем больше я рассказывал о ней, тем больше слышал - "а она работает в многопотоке?". Вот же!..
Вот собственно из-за этого случайным образом возникла идея реализовать что-то похожее - лишь бы работало.
Сразу говорю, что реализовал идею с многопотоком для своей мыши достаточно успешно, на форуме даже видео выложил.
Оказалось вот что: техника, которую я применил к своей мышке, применима ко всему.

Совсем недавно, задавал вопрос по поводу достаточно ли вам, дорогие читатели, многопоточности только в файлах и эмуляции?
Если да, то хорошо, а если нет? Допустим вы работаете с файлом напрямую. Ну например потому что он очень большой и захламлять оперативку вам не охота.
Остановимся на этом случае.
Какой результат нас будет ожидать если мы запустим такой шаблон в несколько потоков? Повезет - не конфликтуют, не повезет - все полетит к чертям.

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

mutex.PNG


Как работать с библиотекой.
Вам нужно реализовать класс Z.ZMutex для последующей блокировки кода и уничтожить его когда проблемное место закончилось.
Конструктор класса ZMutex(string GlobalMutexID, int timeOut) принимает два аргумента:
GlobalMutexID - уникальное имя блокировки, например "ANY_UNIQUE_NAME_FOR_GLOBAL_MUTEX__1234567890"
timeOut - тайм аут ожидания освобождения ресурса. Если меньше 0, мьютекс будет ждать до бесконечности пока не освободится ресурс.
Если timeOut = N > 0, то по истечении времени ZMutex выдаст исключение о том что он ждал, но не дождался.

Продемонстрирую простейший пример в С# сниппете:

using (var mymutex = new Z.ZMutex("ANY_UNIQUE_NAME_FOR_GLOBAL_MUTEX__1234567890", -1))
{
//здесь и будет весь ваш небезопасный код
System.IO.File.AppendAllText(project.Directory+"\\test.txt", "asdfasdfasdfasdf23r2r2rqwfwoifjof"+Environment.NewLine, Encoding.UTF8-);
}


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

string GlobalMutexID = "ANY_UNIQUE_NAME_FOR_GLOBAL_MUTEX__1234567890";
int timeOut = -1; //-1 - wait infinitely; if timeOut is > 0, waits timeOut ms and than raise TimeoutException
project.Context["mutex"] = new Z.ZMutex(GlobalMutexID, timeOut);


а по окончании

var mutex = (Z.ZMutex)project.Context["mutex"];
mutex.Dispose();


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

Вот и все.
Пригодится - хорошо, нет - тоже хорошо.
 
Номер конкурса статей
  1. Второй конкурс статей
Тема статьи
  1. Нестандартные хаки

Вложения

Последнее редактирование модератором:
В BadEnd надо обязательно запихивать вызов
C#:
Развернуть Свернуть Копировать
var mutex = (Z.ZMutex)project.Context["mutex"];
mutex.Dispose();
Иначе может случить ситуация, что проект упадет ДО вызова разблокировки mutex
 
  • Спасибо
Реакции: Yuriy Zymlex
спасибо darkdiver за замечание и спасибо что добавили в конкурс.
Добавлю лишь что BadEnd макрос не знает произошло ли освобождение ресурсов. Поэтом для каждого мутекса нужно проделывать следующее.
var mutex = (Z.ZMutex)project.Context["mutex"];
if(mutex != null) mutex.Dispose();
Не знаю насчет контекста, возращает ли он null если обьекта нет, во всяком слчае все равно нужно как-то проверять.

Мне будет очень приятно услышать от всех, кому пригодился данный метод, где он применился.
Вариантов для применения много: буфер обмена, работа с любыми файлам, программами, распределение нагрузгки на процессор, память, сеть...
 
Последнее редактирование:
Круто, но я решил это в 3 кубика через глобальные )
 
изначально я вписал мутекс в свою либу, а там нет зависимостей от зенки.
какое ожидание ставить, если ресурс блокируется на милисекунды?...
 
А можно по подробнее о том что все-таки мютекс делает? Как я понял:
У нас есть шаб с мютексом, работающий в 2 потока.
1-й поток подходит, например, к участку где идет запись в файл. Здесь создается новый мютекс, который блокирует файл или не файл все же? И 2-й поток дойдя до этого места будет ждать пока снимется блокировка или какое-то максимальное время?
 
Что будет с данными, если 2-й поток прождал, например, 30 секунд а блокировка так и не снялась. Подозреваю, что будет потеря данных из 2-го потока.
 
Но так как у вас написано, что по истечении тайм аута мютекс выдает исключение - выходит, что это самое исключение можно выловить шабом и остановить проект, чтобы не терялись данные
 
мутекс не блокирует сам файл или любой другой ресурс, а только остальные процессы с таким же мутексом.
Если любой другой процесс обратится без мутекса, то в худшем случае будет конфликт, если он предусмотрен на более низком уровне.
с таймаутом все верно.
 
Спасибо. Тогда возникает вопрос о глобальном ID мутекса - он должен быть одинаковым или унакальным в пределах потока/шаблона/системы?
 
должен быть уникальным в пределах системы. Любой процесс или поток с одинаковым мутексом будут работать слаженно.
Если пишете свою либу и есть критический ресус - используйте класс прямо в либе.
 
Я просто над шабами для зенно задумываюсь. Почти кажен шаб читает с файла номер строки, увеличивает его на 1 и записывает обратно. Бывает что Несколько потоков успевают одно и тоже значение считать с файла.
Как я понимаю, чтобы потоки ждали в очереди чтобы считать/записать файл, то для каждого потока ИД мутекса должен быть уникальным. И надо для ИД генерировать какой-то UUID?
 
для каждого проекта ид должен быть уникальным. В пределах одного проека ид должен быть постоянным.
Может вам лучше и проще воспользоваться глобальными переменными? Я не знаю насколько это эффективно, но люди пользуются.
Насколько я представляю, там такая логика. Делаете уникальное имя окружения плюс уникальное имя для глобальной переменной. Она будет видна из всех шаблонов. Работаете с ней как с переключателем - 0\1. Если 0 - меняете значение на 1, выполняете там что вам нужно и возвращаете в 0. Если изначально значение 1, ждете пока не будет 0.
 
  • Спасибо
Реакции: Vik89
Весьма полезная статья, проголосовал! :ay:

Я думаю такой функционал надо пихать в зеннку в виде куба.
Точно! Куб, как простой способ ограничения потоков без всяких переменных. Жаль, что customactions не предусматриваются, приходится для нетривиальных задач лезть в обход.
 
спасибо большое, мне куда приятней знать, что это хоть кому-то нужно.

кстати, кто не знает, у каждого снипета есть свой SyncObject, который по сравнению с глобальными переменными работает куда лучше, но не на 100% В принципе если вам не нужно по 1000 раз в секунду перезаписывать файл, вполне достаточно и этого. Для других процессов растянутых во времени это ничем не грозит. Единственный минус лочится только снипет а не часть кода.
 
  • Спасибо
Реакции: Adigen
...
Насколько я представляю, там такая логика. Делаете уникальное имя окружения плюс уникальное имя для глобальной переменной. Она будет видна из всех шаблонов. Работаете с ней как с переключателем - 0\1. Если 0 - меняете значение на 1, выполняете там что вам нужно и возвращаете в 0. Если изначально значение 1, ждете пока не будет 0.

в С# не разбираюсь, но за эту идею огромное спасибо. Внедрил у себя)
 
@LexxWork спасибо большое за решение всех проблем с многопотоком =)
Скомпилил либу под х64 - отлично работает.
Стояла задача запуска приложения в 1 экзмпляре в многопоточном проекте.
По логике: первый поток должен проверять запущено ли приложение, и если нет, производить запуск. Остальные потоки, в случае если приложение запущено - просто начинают работу.
Данное решение оказалось единственным работоспособным вариантом.
 
Подскажите как собрать для x64?
Или если не трудно прикрепите готовый dll
 
А как добавить через GAC блок верно?

Видимо что-то я не то делаю ошибка


Код:
Развернуть Свернуть Копировать
Desktop\mutex.dll не является .Net сборкой или не возможно получить доступ к сборке

System.NotSupportedException: An attempt was made to load an assembly from a network location which would have caused the assembly to be sandboxed in previous versions of the .NET Framework. This release of the .NET Framework does not enable CAS policy by default, so this load may be dangerous. If this load is not intended to sandbox the assembly, please enable the loadFromRemoteSources switch. See http://go.microsoft.com/fwlink/?LinkId=155569 for more information.
   at System.Reflection.RuntimeAssembly.nLoadFile(String path, Evidence evidence)
   at System.Reflection.Assembly.LoadFile(String path)
   at ZennoLab.DotNetResolver.DotNetResolver.AddExternalReference(String path)
   at ZennoLab.ProjectMaker.Controls.ProjectEditor.ProjectBar.StaticBlockSettings.GACReferences.H9sM7fbq3clMgl2x3sru(Object , Object )
   at ZennoLab.ProjectMaker.Controls.ProjectEditor.ProjectBar.StaticBlockSettings.GACReferences.fDCs3cknav(Object  , OkButtonClickArgs  )
 
для каждого проекта ид должен быть уникальным. В пределах одного проека ид должен быть постоянным.
Может вам лучше и проще воспользоваться глобальными переменными? Я не знаю насколько это эффективно, но люди пользуются.
Насколько я представляю, там такая логика. Делаете уникальное имя окружения плюс уникальное имя для глобальной переменной. Она будет видна из всех шаблонов. Работаете с ней как с переключателем - 0\1. Если 0 - меняете значение на 1, выполняете там что вам нужно и возвращаете в 0. Если изначально значение 1, ждете пока не будет 0.

эммм. а чем сниппет от C# кода в кубике отличается? или если это одно и тоже, то получается, что лочится все выполнение кода?
 

Кто просматривает тему: (Всего: 0, Пользователи: 0, Гости: 0)