kroki (Все сообщения пользователя)

Выбрать дату в календареВыбрать дату в календаре

Страницы: 1
Интерактивная работа с QLua
 
В догонку: я так понимаю, Quik не закрывает консольные потоки ввода/вывода (иначе и под Linux/Wine бы не работало без их переоткрытия).  Возможно проблема в cmd/command, и быть может поможет установка под Windows нормальной оболочки типа Bash.  А может и нет...
Потоковая безопасность в QLua - документация и реальность
 
У разработчиков на этот счет  другое мнение :

Цитата
Sergey Gorokhov написал:
 
Цитата
Старатель  написал:
Вопрос разработчикам: верно ли, что в QUIK, как таковой, многопоточности нет?
В QUIK есть многопоточность
 
Цитата
Старатель  написал:
Да, есть два потока: основной и main. Но в каждый момент времени работают команды только из одного потока. Просто происходит переключение между потоками на уровне ОС.
так обрабатывается ситуация при одновременном доступе к одному ресурсу
Как раз собственно интерпретатор Lua и является таким ресурсом, которым не получатеся воспользоваться из нескольких потоков одновременно.  Возможно не все разработчики об этом знают, смотрите в код Lua - так надежнее...
Интерактивная работа с QLua
 
Цитата
Старатель написал:
QUIK 7.23.1.4
Скрипт-то запускается, но сразу же останавливается.
cwrite  ничего не кажет.
Выходит по условию
Код
   if  stop  or  c  =  =   nil   or  string_match(c,  "^%s*quit%s*$" )  then   

Да, вы правы.  Под Wine под Linux все работает (пробовал 7.14, 7.16, 7.23), но сейчас попробовал под настоящей виндой в VirtualBox - под Windows в QLua не работает даже такой простой скрипт:
Код
assert(io.write("io.write()\n"))
local r = io.read()
error("io.read() returned "..tostring(r))
io.write() ничего не выводит (хотя и не выдает ошибку), io.read() ничего не читает и возвращает nil (пробовал и под cmd, и под command).  Ну, как тут помочь я не знаю...
Интерактивная работа с QLua
 
Цитата
qt написал:
Не запускался в старой версии, обновился до 7.19.0.51, все равно не запускается. Нажимаю на Запустить и остается остановленным, в логе  пусто.
Проверил кодировку, запустил консоль от админа.
Что то доставить надо?
Доставлять ничего не нужно.  У меня Quik 7.16.3.14, скрипт работает.  Сам Quik нужно запускать из командной строки, если вы так и сделали, то в чем может быть проблема я не знаю...
Обучаю программированию на QLua. С "0" до мульти-таймфреймового, много-параметрического робота.
 
Я здесь потролить, можете не отвечать, если что... :)

Цитата
Евгений написал:
Главное мульти-тайфреймовость и много-параметричность.
"Главное" - для чего?

Цитата
Евгений написал:
Позволял бы квик без костылей подключать роботов на паскале или C#, не нужен был бы Lua.
"You are trying to solve the wrong problem.  When you solve the right problem, the wrong problem will go away."

Попробуйте в свою очередь поискать предложения типа: "Отучаю от Паскаля и C# до C++.  Продвинутых отучаю и от ++".  А уж C подключится в Lua безо всяких костылей ;).

P.S.: в списке суперязыков Бейсик забыли ;)
Странная типизация результата
 
Цитата
Алексей написал:
1. При сравнении двух double чисел последние 16 разрядов мантиссы игнорируются, что соответствует 12-13 значащим цифрам в десятичном представлении числа.
Осмелюсь предположить, что вы ошибаетесь.  Если мы говорим об архитектуре x86, то раньше, когде не было еще всяких MMX/SSE, в процессоре были только 80-битные регистры, в которых производились вычисления как double, так и long double.  И вот для double последние 16 бит игнорировались, чтобы сравнивались только 64 бита.  В QLua вроде все работает, как ожидается:
Код
qlua> 1.0000000000000003 == 1.0000000000000004
false
qlua> 1.0000000000000003 ~= 1.0000000000000004
true
qlua> 1.0000000000000003 < 1.0000000000000004 
true
qlua> 1.0000000000000003 > 1.0000000000000004
false
Или я не прав?


Цитата
На самом деле, при сложении исходно целых чисел ошибка не возникает, а вот при вычитании ошибка ИНОГДА возникает: вы получаете результат на 1 разряд мантиссы меньше, чем должно быть.
А пример такой есть?  Я могу представить ситуацию, если магнитуды чисел существенно отличаются.  Но в общем случае не припомню чего-то подобного (но я и не претендую на глубокое понимание вопроса, мог и не знать).  Пример, или ссылку на почитать, плиз :).


В любом случае, проблему OP вы не адресуете, он уверен, что на входе целые числа (правда какие - не говорит, код - не показывает).  Гипотеза - сборщик мусора подворовывает из результатов вычислений, я так понял (опять же, нам не объясняют как, предлагают просто поверить).  Я бы еще мог предположить, что подключается какой-то дикий модуль на C, который плугом вспахивает всю память, но тогда бы все просто взорвалось, а вот вместо 2 получить 1.999999999991 - это почерк прфессионала.  Suntor написал пример, да вот беда: результат первого вычисления x = 10 - будет "подпоркой" для второго (при x=10 все работает - доказано!), результат второго опять x = 10 - будет "подпоркой" для третьего, и сборщик мусора ничего не украдет :(.  Тут нужно глубже копать, читать Иерусалимски, разбираться, откуда растет утверждение
Цитата
Andrei2016 написал:
Была бы в Lua четкая типизация данных, такого эффекта не могло бы быть в принципе.
Жаль, что все идет в виде union.
Ну и радоваться, что нам достались версии Quik безо всех этих проблем :).
Странная типизация результата
 
Забыл про эту часть:
Цитата
Andrei2016 написал:
Мои опыты показали, при падении объема скрипта до уровня примерно 50К-60К (просто за счет физического убирания нескольких процедур, при оставлении неизменным содержимого сбойной операции), все становится идеально.
То, что QLua в плане своей "аппаратной" (назовем так) реализации существенно отличается от того же Lua for Windows, - это уже и проверенный, и доказанный не только мною факт. Зачем опровергать очеивдное?
Ммм, хотя что тут можно сказать?  В МИД Англии или США вас ждет прекрасное будушее :).  На наших же просторах доказательства нужно приводить, а не только утверждать, что они у вас есть.  Есть что-то там "очевидно" - пожалуйста, покажите.
Странная типизация результата
 
Цитата
Andrei2016 написал:
проблема в том, что эффект проявился на 11-м знаке, а не на 17-м:
вместо 2 message выдал 1.999999999991. Это первое.
Происходит следующее: у вас после 14-го знака есть дробная часть, но вы своим "элементарным" тестом ее не видите из-за округления.  После умножения на param эта дробная часть передвигается в 11-й знак, вы начинаете ее видеть и дивиться.

Цитата
Второе. Я же четко написал: присваивается операнду ЦЕЛОЧИСЛЕННОЕ значение, т.е. условно "x=10".
Нет, вы вполне "четко" написали, что вы думаете, что присваивается целое, а на вопрос, почему вы так думаете, вы показали неверную проверку.  Вы думаете, что
Цитата
Andrei2016 написал:
Т.е., пишу я, скажем, x = 3. Да, по факту оно хранится как double (допустим, как 3,000000000001). Но для пользователя это трактуется как 3.
а это вообще нонсенс.  Фактически вы и утверждаете, что в вашей терминологии 3,000000000001 - целое.  Поэтому не нужно никаких "условно x=10", прямо перед строкой x = (y-z)*param сделайте присвоение своих целых значений всем переменным, и убедитесь, что результат - тоже целое.  Или проверьте, что y == math.floor(y) (и также для z и param).  Ну или покажите эти числа, как вам уже дважды предложили.  Это, конечно, если вы хотите решить свою проблему, а не продолжать заблуждаться, будто компьютер вас обманывает...
Странная типизация результата
 
Цитата
Andrei2016 написал:
проверяется элементарно через message("x= "..x.."  y= "..y.."  z= "..z.."  param= "..param). Можете убедиться сами.
Проверил.  При переводе числа в строку Lua по умолчанию округляет до 14 знаков (ну и отбрасывает хвостовые нули, формат "%.14g").  Однако в double знаков, имеющих значение - 17.  Поэтому то, что в выдаче message() вы видите как "целое" число, на самом деле таковым может и не являться.  Можете поверить:
Код
local x = 1.000000000000007
if x ~= 1 then
   message("x="..x..", real x="..string.format("%.17g", x))
end
Сообщение будет выведено, ибо x не равен 1.  Но в самом сообщении в "элементарной" его части будет написано, что равен (в правильной - реальное значение, которое не равно и исходному, ибо оно не представимо точно).  Вооружившись этим знанием, перепроверьте все ваши скрипты по 300K - возможно, найдете и другие заблуждения, и вторая проблема тоже уйдет ;) (я с ней помочь не смогу (думаю, что и другие тоже), но стою на своем: ошибка у вас, не в Lua - и вам только кажется, что факт запуска Lua под Quik что-то меняет в плане корректности выполнения).
Странная типизация результата
 
Цитата
Suntor написал:
поправлю, в ±2^53, если быть более точным
Точно, 52+1.  И "гипотеза" через "е" (и может что еще).

А ведь мог бы сказать, что "у меня было все правильно, но в браузере ошибка, и при передаче текста он..." :)
Странная типизация результата
 
^ что-то пошло не так (видимо, слеш в конце цитаты), жалко, что нет возможности редактировать посты.

Цитата
s_mike@rambler.ru написал:
умножение и деление, не говоря уже о всяких корнях, в большинстве случаев уже даст накопившуюся ошибку.
Умножение целого на целое ошибки не даст (опять же, если результат влезает в +-2^54, как вы правильно заметили).  Я думаю, что Andrei2016 просто ошибается либо в том, что на входе в x = (y-z)*param у него целые, либо в том, что на выходе не целое.  Это вообще типично: приходит человек, и делает утверждение, "Я написал скрипт, и абсолютно уверен, что он правильный.  А результат - неверный!  Скажите, это что не работает: Lua, Quik, или компьютер?"

Цитата
Andrei2016 написал:
По моим исследованиям, как только скрипт (основной плюс подключаемые через dofile), превышает суммарно некоторый объем, при выполнении скрипта начинаются какие-то совершенно несуразные ошибки.

В этой связи, хочу еще спросить у разработчиков: какой суммарный объем скрипта в килобайтах обеспечивает безошибочную работу в рамках терминала QUIK? То, что есть предел определенный - несомненно.
Предлагаю рассмотреть другую гипотизу: вы пишите небольшой скрипт, делаете небольшое количество ошибок, все более-менее работает.  Вы пишите большой скрипт, делаете большое количество ошибок - все перестает работать.  Уверенность в отсутствии ошибок в собственном коде - основная помеха их исправлению...
Странная типизация результата
 
Цитата
Andrei2016 написал: Т.е., пишу я, скажем, x = 3. Да, по факту оно хранится как double (допустим, как 3,000000000001). Но для пользователя это трактуется как 3. И если я проведу сравнение (x == 3), то получу значение true, а не false, как было бы в случае с чистым double/
Из того, что вы пишите, видно, что вы заблуждаетесь (про связь размера скрипта и точности вычислений - вообще нонсенс).  Но чтобы вам это объяснить нужно, чтобы вы сначала показали конктерные цифры, с которыми вы оперировали, и как именно вы определяли, что в последнем x - double, а не "integer".
status == 15 в OnTransReply
 
Когда-то сам пытался спросить вот здесь, в итоге сделал вывод, что 15 никто никогда не видел.  Мой робот его тоже пока не встречал...
Защита кода QLua от компрометации, Есть ли возможность исключить доступ к коду на QLua брокера и разработчиков Quik
 
Можно еще запустить Quik в отдельной виртуальной машине, а робота - вовне, и общаться через сокет.  А то вдруг Quik вообще любые файлы с вашей машины отсылает брокеру - потенциально может ведь :).
Защита кода QLua от компрометации, Есть ли возможность исключить доступ к коду на QLua брокера и разработчиков Quik
 
Цитата
Анатолий написал:
Планирую создать робота для торговли через терминал Quik.
Поверьте, когда вы его допишите вы узнаете, что защищать там было особо и нечего :).  Тут многие поначалу думают, что напишут роботов, которые им заработают миллиард-два.  Некоторые потом еще пытаются продать другим (как в той притче про "Искусство убивать драконов").  Хотя опять же, некоторые из таких товарищей как раз и прячут код, чтобы не было очевидно, что он, собственно, ничего не делает...

Но тем не менее, по вашему вопросу: формально терминал Quik загружает файл с вашим кодом и может сделать с ним все, что угодно, в том числе передать по сети.  Некоторые предлагают компилировать сишный код - не поможет, читать ассемблер не так уж и сложно.  Всякая обфускация (то есть намеренное усложнение и запутывание кода) тоже не поможет, ибо для reverse engineering важно только то, что код делает, а не то, как именно он написан.  А что код делает - это можно узнать, прогоняя его на туче примеров и параллельно подсматривая врутрь дебагером (что можно и не полениться сделать для робота, который уверенно генерирует прибыль).  Да и вообще, зачем код изучать?  Украл, запустил, уехал на острова...

Поэтому если ваш код выполняется терминалом Quik то он по определению потенциально скомпромитирован.  Отсюда вывод: нужно выполнять его в другом процессе, с которым QLua будет общаться через, например, named pipes или socket.  Но даже и тогда, если вы и запустете все это на настоящей операционной системе (а не Windows, которая даже названием выбрала образ эдакой большой дыры в стене) - все равно гарантий не будет, ибо в код системы, даже если он открытый, вы едва ли заглядывали, так что это будет скорее вопрос самоуспокоения, а не реальной защиты.

В общем, оно того не стоит, но если вам очень хочется, то выполняйте робота в отдельном процессе.
Где ошибка при заполнении таблица для стоп лосса и тейк профита ?
 
Код
       SEC_CODE = sec_code,
Разве не SECCODE?
invalid key to next
 
М-да, перечитал документацию по next() и понял, что с кодом опять напутал (next() возвращает больше одного значения).  Нужно делать вот так:
Код
do
   local orig_next = next
   function next(t, k)
      local ok, nk, nv = pcall(orig_next, t, k)
      if not ok then
         message(debug.traceback())
         error(nk, 0)
      end
      return nk, nv
   end
end
То есть перегружаем next(), внутри вызываем исходную функцию под pcall(), если она выдала ошибку, то выдаем стек и бросаем исключение, иначе возвращаем оба результата исходной next().
invalid key to next
 
Внезапно осознал, что предложенный мною код не сработает в случае, если вы в цикле удаляете ключи (что в Lua разрешено).  Так что правильнее будет
Код
do
   local orig_next = next
   function next(t, k)
      local r, e = pcall(orig_next, t, k)
      if e then
         message(debug.traceback())
         error(e, 0)
      end
      return r
   end
end
invalid key to next
 
Скорее всего ошибка уже в том, что вы используете next() там, где следовало бы использовать pairs().  Выше вам предлагали код для проверки целочисленных индексов, а у вас, видимо, нечисловые ключи.  Если нужно найти конкретное место возникновения, то добавьте в начало файла:
Код
do
   local orig_next = next
   function next(t, i)
      if i ~= nil and t[i] == nil then
         message(debug.traceback())
      end
      return orig_next(t, i)
   end
end
Будет выдано сообщение со стеком того места, где вы вызываете next() с несуществующим ключем (код не проверял, но вроде сработает).
Некорректное исполнение, При отсылке лимитной заявки на покупку получаю некорректную цену покупки
 
Цитата
Евгений написал:
Опционный робот выставил лимитную заявку на покупку 12 контрактов RI125000BN8D путов по 870 пунктов.
Но в таблице сделок вывелось что часть контрактов исполнилось по 920 пунктов. Как такое вообще может быть для лимитного ордера?
Думаю, что вероятность того, что вы неверно интерпретируете работу своего скрипта все же выше, чем вероятность того, что на бирже произошел сбой.  Перепроверьте ваши утверждения о том, что заявка действительно была выставлена по 870 (а не по 920), или что сделка по 920 действительно соответствует заявке по 870 (а не какой-то еще).  В приведенном вами коде есть одна потенциальная проблема: если две разные заявки будут выставлены в одну и ту же секунду, то у них будут одинаковые TRANS_ID - быть может нужо копать в этой окрестности...
тестовый квик не учитывает размер лота в getDepoEx и OnDepoLimit
 
Цитата
Sergey Gorokhov написал:
Логично задать вопрос не сюда, а напрямую брокеру.
Мой брокер эту настройку не использует.  Вполне возможно, что не только мой, а вообще ни один брокер не знает ответа на вопрос
Цитата
kroki написал:
что тогда будет в  currentbal , вещественное число?
ибо больше смахивает на очередной случай "мы и сами не знаем, зачем мы ее сделали, не нравится - не пользуйтесь" ;).
тестовый квик не учитывает размер лота в getDepoEx и OnDepoLimit
 
Цитата
Sergey Gorokhov написал:
Может быть в лотах, может быть в штуках. Каждый брокер настраивает на свое усмотрение.
Это точно так?  Если брокер "настроит в лотах", то как работать с неполными лотами?  Даже если ими не торговать, после увеличения размера лота на бирже (иногда случается) на счете могут возникнуть неполные лоты - что тогда будет в currentbal, вещественное число?
Как проверить работу примера?, QLUA.chm
 
Список того, что должно быть в индикаторе есть в документации (QLUA.chm) в разделе "Индикаторы технического анализа -> Общие сведения".  Но вы смотрите не с того угла.  Есть две среды выполнения, для индикаторов, и для "просто скриптов".  Среда для индикаторов ожидает увидеть некий набор функций и переменных (Settings.Name, Init(), OnCalculate()), которые она будет дергать.  Среда для "просто скриптов" ожидает другой набор (пустой, но по вкусу можно добавить main(), OnInit(), OnConnect(), и т.д.).  Если минимальный набор в коде присутствует, среда его выполняет, то есть один и тот же скрипт можно выполнять и там, и там, если нужные функции присутствуют.
Ошибка при чтении стакана
 
Цитата
_sk_ написал:
А зачем Вам "дальний край" стакана value.tbl.bid[indexBid].price ? Обычно value.tbl.bid[1].price более важное значение.
Это место как раз правильное.  Стакан тупо упорядочен по цене, об удобстве никто не думал, поэтому лучшее предложение в начале массива offer (наименьшая цена), а лучший спрос - в конце массива bid (наибольшая цена).
Как проверить работу примера?, QLUA.chm
 
Это не индикатор, а "просто скрипт".  В моей версии Quik это меню "Сервисы -> Lua скрипты...", далее кнопка "Добавить", ищем файл, выбираем его в списке, кнопка "Запустить".
Ошибка при чтении стакана
 
В документации есть нюанс, в который трудно поверить, но тем не менее работает именно так, как написано.  Читаем внимательно про getQuoteLevel2():
Цитата
При отсутствии и спроса и предложения функция возвращает таблицу без параметров bid и offer.
Имеется ввиду, что нет ни спроса, ни предложения.  Буквально.  Если же предложение есть, а спроса нет, то:
Цитата
bid_count  STRING  Количество котировок покупки. При отсутствии спроса возвращается значение «0»
и
Цитата
bid  TABLE  Котировки спроса (покупки). При отсутствии спроса возвращается пустая строка
Внезапно!

В вашем случа видимо происходит следующее (кстати, Subscribe_Level_II_Quotes() можно и нужно делать только один раз, а ни при каждом вызове):
Код
if indexBid ~= nil then  -- 0 ~= nil - истина
   if value.tbl.bid then  -- пустая строка в Lua это истина
      bidPrice = tonumber(value.tbl.bid[indexBid].price)  -- пытаемся индексировать не таблицу, а строку - ошибка 
OnTrade для сделок по FUTSPREAD
 
Цитата
lergen написал:
Поэтому вопрос - это случайность или закономерность?
Ответ мне не известен, но при любом раскладе придется быть готовым к аномалиям.  Вообще, совет во многих ветках один: чем меньше предположений вы сделаете, тем дольше проживет ваш депозит ;).

Недавно был упомянут очень хороший пост (вот этот), буквально ему следовать может и не стоит, но можно взять его за основу, и упрощать те моменты, в детерминированной работе которых вы уверены (например, вы используете market orders, значит сложности отслеживания жизненного цикла лимитированных заявок отпадают).
OnTrade для сделок по FUTSPREAD
 
Я хочу сказать, что ваш "фильтр для отсеивания  повторных вызовов с одинаковыми номерами" не работает в общем случае.  Возможно проблема в этом, возможно я не понял ваш вопрос.
OnTrade для сделок по FUTSPREAD
 
В коде вы полагаетесь, что повторные вызовы придут до новых, а это не обязательно так, ситуация "A, B, еще раз A, еще раз B" тоже возможна.  Попробуйте
Код
local trades = {}

function OnTrade(trade)
   local tradenum = trade.tradenum
   if trades[tradenum] then
      return
   end
   trades[tradenum] = true
   ...
OnTransReply status, Какие статусы нетерминальные?
 
_sk_,тогда как вы рекомендуете реагировать на «0», «1» и «15»?  Так же, как и на «3», то есть ожидать OnOrder?  По описанию статусов мне казалось, что эти три не являются ни ошибкой, ни подтверждением, что все хорошо.

За ссылку спасибо, задал там вопрос...
Порядок отслеживания процесса выполнения транзакций
 
Цитата
_sk_ написал:
3) Нужно следить за UID экземпляра QUIK, чтобы фильтровать "чужие" заявки.
Насколько "чужие" эти "чужие" заявки?  Из других терминалов того же пользователя (типа WebQuik)?  Или еще какие-то случаи бывают?
OnTransReply status, Какие статусы нетерминальные?
 
И еще в догонку вопрос: если sendTransaction() возвратил непустую строку, то ошибка, и вызова OnTransReply() не будет.  Так?
OnTransReply status, Какие статусы нетерминальные?
 
Документация:
Цитата
Статус транзакции. Возможные значения:
«0» – транзакция отправлена серверу;
«1» – транзакция получена на сервер QUIK от клиента;
«2» – ошибка при передаче транзакции в торговую систему. Так как отсутствует подключение шлюза Московской Биржи, повторно транзакция не отправляется;
«3» – транзакция выполнена;
«4» – транзакция не выполнена торговой системой. Более подробное описание ошибки отражается в поле «Сообщение»;
«5» – транзакция не прошла проверку сервера QUIK по каким-либо критериям. Например, проверку на наличие прав у пользователя на отправку транзакции данного типа;
«6» – транзакция не прошла проверку лимитов сервера QUIK;
«10» – транзакция не поддерживается торговой системой;
«11» – транзакция не прошла проверку правильности электронной цифровой подписи;
«12» – не удалось дождаться ответа на транзакцию, т.к. истек таймаут ожидания. Может возникнуть при подаче транзакций из QPILE;
«13» – транзакция отвергнута, так как ее выполнение могло привести к кросс-сделке (т.е. сделке с тем же самым клиентским счетом);
«14» – транзакция не прошла контроль дополнительных ограничений;
«15» – транзакция принята после нарушения дополнительных ограничений;
«16» – транзакция отменена пользователем в ходе проверки дополнительных ограничений

Я правильно понимаю, что «0», «1» и «15» являются нетерминальными статусами, то есть после их получения будет еще вызов OnTransReply() с другим статусом - так?  Остальные все терминальные; все, кроме «3» - ошибка (плюс «12» в QLua не бывает).  Все верно?

Если не трудно, плиз опишите, когда возникают «0», «1» и «15» (в моей версии Quik «0» и «1» не замечал, ограничения для «15» видимо не задавал).
Гарантируется ли упорядоченность результата getQuoteLevel2()?
 
Sergey Gorokhov,ну, вы ведь могли упорядочивать после передачи данных в callback, прямо перед отображением.  Но я ваш ответ понимаю как "да, гарантируется".  Спасибо!
Каков реальный сценарий вызова OnDepoLimitDelete(), OnMoneyLimitDelete(), OnFuturesLimitDelete()?
 
В каких реальных случаях вызываются OnDepoLimitDelete(), OnMoneyLimitDelete(), OnFuturesLimitDelete() (первые два для limit_kind >= 0, внутренние не особо интересны)?  Кто-нибудь встречал эти вызовы в реале?

Кстати, а зачем вызываются ползовательские callbacks с limit_kind < 0?  Какие осмысленные действия он бы мог сделать (хотя бы в теории)?
Коллбеки Луа, Может ли таблицы получаемые коллбеками быть массивом ?
 
Если я правильно понял вопрос, то ответ - вариант 1.  То есть в callback передается хеш - множество пар "ключ-значение".  Значение - либо скаляр (строка, число), либо тоже хеш (например поле datetime на входе в OnTrade()).  Собственно массивы на вскидку вроде есть только в выдаче getQuoteLevel2().
Гарантируется ли упорядоченность результата getQuoteLevel2()?
 
Сейчас в выдаче getQuoteLevel2() массивы bid и offer упорядочены по цене, но в документации это не упомянуто.  Гарантируется ли такая упорядоченность, или опираться на это нельзя?  Заранее спасибо за ответ!
Интерактивная работа с QLua
 
Цитата
kroki написал:
Код
qlua> OnPa ram = nil
Ага, вот такие вот пробелы и появляются - почему?! :(
Интерактивная работа с QLua
 
Написал себе скрипт для интерактивной работы с QLua, возможно пригодится кому-то еще (иногда бывает нужно что-то быстро попробовать не строча полноценный скрипт).  Код лежит на GitHub (хотел разместить прямо здесь, но (по крайней мене при нажатии кнопки "Просмотр") в случайных местах кода появлялись символы пробела - не понял, почему).

Для использования запускаем Quik из командной строки.  То есть открываем окошко для ввода команд, пишем:
Код
C:\Windows\system32> chdir \Path\to\Quik\dir
C:\Path\to\Quik\dir> info.exe
Далее из Quik запускаем qlua-console.lua, в терминале появляется приглашение для ввода.  Можно исполнять любой код Lua, вызывать функции QLua и определять callbacks.  Дополнительно доступна функция qluaDump(...) - возвращает строковое представление переданных аргументов.  Например:
Код
qlua> 2 + 3
5
qlua> getClassesList()
'CROSSRATE,EQOB,RPMA,RPMO,EQDB,SMAL,EQQI,OQQI,INDX,TQBR,TQOB,TQQI,TQDE,SPBFUT,SPBOPT,RTSIDX,CETS,INSTR,EQRP_INFO,TQIF,TQTF,INSTRCETS,'
qlua> getSecurityInfo("TQBR", "GAZP")
{ sec_code='GAZP', face_unit='SUR', class_code='TQBR', code='GAZP', scale=2, face_value=5, short_name='ГАЗПРОМ ао', lot_size=10, name='"Газпром" (ПАО) ао', min_price_step=0.01, isin_code='RU0007661625', class_name='МБ ФР: Т+ Акции и ДР', mat_date=20171220 }
qlua> function OnParam(c, s) 
   2> print(qluaDump(c, s, getParamEx2(c, s, "LAST")))
   3> end
qlua> 
и в терминал посыплются строки вида
Код
'TQBR', 'TRNFP', { param_type='1', param_value='178600.000000', result='1', param_image='178 600' }
'TQBR', 'VTBR', { param_type='1', param_value='0.046810', result='1', param_image='0,04681' }
'TQOB', 'SU24019RMFS0', { param_type='1', param_value='102.537000', result='1', param_image='102,5370' }
'CETS', 'EURUSD000TOM', { param_type='1', param_value='1.193400', result='1', param_image='1,193400' }
'TQOB', 'SU26219RMFS4', { param_type='1', param_value='102.300000', result='1', param_image='102,3000' }
'TQBR', 'SBER', { param_type='1', param_value='223.330000', result='1', param_image='223,33' }
'TQBR', 'SNGSP', { param_type='1', param_value='28.005000', result='1', param_image='28,005' }
'TQBR', 'RSTI', { param_type='1', param_value='0.820100', result='1', param_image='0,8201' }
'CETS', 'EURUSD000TOM', { param_type='1', param_value='1.193400', result='1', param_image='1,193400' }
'TQBR', 'AFLT', { param_type='1', param_value='138.950000', result='1', param_image='138,95' }
'TQBR', 'GAZP', { param_type='1', param_value='130.300000', result='1', param_image='130,30' }
'TQBR', 'HYDR', { param_type='1', param_value='0.727000', result='1', param_image='0,7270' }

Для остановки callback присваиваем ему nil (бесстрашно вводим код пока в терминале продолжается выхлоп от OnParam - в реале, конечно, нужно в файл выводить):
Код
qlua> OnPa ram = nil
qlua>
(выхлоп прекращается).  Ну и т.д.

Для завершения работы набираем "quit":
Код
qlua> quit
qlua terminated
Если вдруг по привычке нажмете кнопку "Остановить", то нужно будет в терминале нажать <Enter>, чтобы main() вышла из io.stdin:read() (не нашел изящного способа это побороть, не прибегая к внешним библиотекам).

Следует заметить, что локальные переменные видны только до следующего qlua> (они локальны внутри chunk), поэтому
Код
qlua> local v = 5
qlua> print(v)
nil
qlua>
Но:
Код
qlua> local v = 5; print(v)
5
qlua> do
   2> local v = 5
   3> print(v)
   4> end
5
qlua>
Все как и в обычном интерпретаторе Lua, запущеном в интерактивном режиме.
Очистка таблиц Quikа по OnCleanUp может вырубить скрипт. Как уберечься?
 
Цитата
Sergey Gorokhov написал:
Версия очень старая, рекомендуется обновить до последней 7.14
Sergey Gorokhov, я бы с радостью обновился, но как?  Из этой ветки я сделал вывод, что нет гарантий, что новая версия терминала будет работать со старой версией сервера у брокера.  Также на вашем сайте сказано, что:
Цитата
Дистрибутив может использоваться для подключения к серверам Технического центра ARQA Technologies. Для подключения к серверам брокеров необходимо использовать дистрибутив, предоставленный брокером.
Или это все устаревшая информация, и можно смело обновяться?  И есть ли где-нибудь пошаговая инструкция?  Ибо на само деле пробовал обновить, но не получилось...
Очистка таблиц Quikа по OnCleanUp может вырубить скрипт. Как уберечься?
 
Алексей, для выяснения, останавливает ли ошибка в callback скрипт, можно запустить
Код
function OnConnected()
   error("OnConnected")
end

function OnDisconnected()
   error("OnDisconnected")
end

local stop = false
function OnStop()
   stop = true
end

function main()
   while not stop do sleep(100) end
end
и перелогиниться.  Для моей версии 7.5.0.72 - нет, не останавливает.
Потоковая безопасность в QLua - документация и реальность
 
Стал программировать, руководствуясь своей "докой" выше, и уперся в противоречие: с одной стороны утверждаю, что GC всегда работает при удерживаемой блокировке (что правда), с другой стороны collectgarbage() - функция стандартной библиотеки Lua, и я написал, что таковые работают не через Lua C API, и потому не потокобезопасны.

Так вот последнее было не верно: пункт 6 меняем на:

6. Функции стандартной библиотеки Lua (такие, как table.sort) являются "Си" функциями.

Точка.  То есть как и остальные "Си" функции они работают через Lua C API, который делает lua_lock()/lua_unlock() в нужных местах.  Но их работа не атомарна.  Например, реализация table.concat() делает (на Си)
Код
    static int tconcat (lua_State *L) {
      luaL_Buffer b;
      size_t lsep;
      int i, last;
      const char *sep = luaL_optlstring(L, 2, "", &lsep);
      luaL_checktype(L, 1, LUA_TTABLE);
      i = luaL_optint(L, 3, 1);
A:    last = luaL_opt(L, luaL_checkint, 4, luaL_getn(L, 1));
      luaL_buffinit(L, &b);
      for (; i <= last; i++) {
        lua_rawgeti(L, 1, i);
B:      luaL_argcheck(L, lua_isstring(L, -1), 1, "table contains non-strings");
        luaL_addvalue(&b);
        if (i != last)
          luaL_addlstring(&b, sep, lsep);
      }
      luaL_pushresult(&b);
      return 1;
    }
то есть в строке A запоминает количество элементов, а в строке B проверяется, что очередной элемент - строка.  Но блокировка захватывается только на время вызова функций Lua C API (например, lua_rawgeti() в коде выше - Михаил как раз об этом и писал), а во время собственно работы цикла не удерживается.  Это значит, что другой поток может удалить какие-то элементы из массива, сделав его короче, но код все еще помнит старый размер, и тогда на очередной итерации цикл вытащит nil, и проверка типа "строка" бросит исключение "table contains non-strings".

То есть обертка table.sconcat() нужна для обеспечения атомарности работы (потокобезопасность в строгом смысле уже была и в оригинальной функции).

Вообще производительность Lua при заданных макросах lua_lock()/lua_unlock() просто ужасная, почти на каждом шагу освобождение и захват блокировки.  Поэтому у себя обернул все критические callbacks в table.ssort(), чтобы не было постоянной борьбы за блокировку с main().  Кстати, в некоторых старых версиях Quik (например, 7.5.0.72) все callbacks вызывались при удержанной блокирокве, но в 7.14.1.7 это уже не так.

Ниже тест "многопоточной" скорости выполнения.  Если запустить скрипт и дождаться окончания работы, но борьбы за блокировку нет.  Если же запустить скрипт и тут же нажать кнопку "Остановить", то будет борьба, и время выпонения цикла в main() существенно возрастет:
Код
local total = 100000000

function OnStop()
   local var
   for i = 1, total do
      var = i
   end

   return 15000
end

function main()
   local start = os.clock()

   local var
   for i = 1, total do
      var = i
   end

   local elapsed = os.clock() - start
   message("CPU time: "..elapsed)
end
Потоковая безопасность в QLua - документация и реальность
 
Сегодня сделал несколько тестов и просмотрел код Lua, и наконец понял.  Быть может будет полезно кому-то еще (ну и чтобы закрыть ветку):

0. В реализации Lua есть макросы lua_lock() и lua_unlock(), которые вызываются в коде Lua в нужных местах, но по умолчанию ничего не делают.  QLua переопределяет эти макросы как вызовы EnterCriticalSection() и LeaveCriticalSection() - захват и освобождние рекурсивной блокировки (сам Lua рекурсивности не требует, но она нужна для реализации table.ssort()).

1. Перед запуском Lua программы реализация Lua делает вызов lua_lock(), то есть инструкции Lua программы выполняются при удерживаемой блокировке.  Это значит, что хотя "потоки" - это настояшие потоки операционной системы, в каждый момент времени интерпретатор Lua работает только в одном потоке, параллельной работы интерпретаторов нет.

2. После некоторых инструкий Lua программы интерпретатор делает
Код
lua_unlock();
lua_lock();
то есть блокировка освобождается и тут же делается попытка захватить ее назад, но если другой поток ожидал блокировку, то он ее захватит первым и будет выполняться до тех пор, пока сам не сделает
Код
lua_unlock();
lua_lock();
(или не вызовет "Си" функцию - смотрим ниже) во время чего первый поток сможет захватить блокировку опять, и т.д.

3. Garbage collector всегда вызывается при удерживаемой блокировке.

4. Вызов "Си" функции из Lua программы (то есть функции, написанной не на Lua, а на каком-то другом языке) выполняется так:
Код
lua_unlock();
call external С function;
lua_lock();
То есть "Си" функции выполняются без удержания блокировки.

5. Все функции Lua C API (то есть те, которые могут вызываться из "Си" функций) - потокобезопасные, то есть делают lua_lock()/lua_unlock() где это необходимо.

6. Функции стандартной библиотеки Lua (такие, как table.sort) являются "Си" функциями, но не работают через Lua C API (дергают внутренний код напрямую) и не делают lua_lock()/lua_unlock() - то есть не являются потокобезопасными. Именно поэтому в QLua добавлены потокобезопасные аналоги некоторых функций, которые просто оборачивают своих младших братьев в lua_lock()/lua_unlock().


Теперь переводим документацию:
Цитата
Одновременная работа с таблицами из функций обратного вызова скрипта и функции main() может приводить к неопределенным ситуациям.
следует читать
Цитата
Одновременная работа с таблицами с использованием функций стандартной библиотеки Lua из функций обратного вызова скрипта и функции main() может приводить к неопределенным ситуациям.
То есть, функциями table.sort, unpack и т.п. работать с таблицей (потенциально) одновременно из разных потоков нельзя (нужно использовать потокобезопасные замены).  Но вот просто код Lua выполнять можно, то есть можно писать
Код
shared_table[key] = val
global_var = nil
и т.п. - здесь функции библитотеки не задействованы, а инструкции программы Lua выполняются с удержанием блокировки.  Также можно использовать "Си" функции сторонних библиотек - они работают через Lua C API, а, значит, потокобезопасны.  Только не нужно путать потоковую безопасность и атомарность: если "Си" функция обещает вставить в какую-то таблицу 5 элементов, вполне возможно, что другой поток увидит, что пока вставлено только 3 элемента.

Цитата
Выполнение потокобезопасной функции блокирует выполнение кода в другом потоке до окончания работы функции.
следует читать
Цитата
Выполнение потокобезопасной функции блокирует выполнение Lua кода в другом потоке до окончания работы функции.
То есть, потокобезопасная функция удерживает блокировку и не дает ее другому интерпретатору, но если в другом потоке уже началось выполнение "Си" функции, то оно продолжится либо до завершения, либо до первого вызова Lua C API, и только тогда будет сделана попытка захватить блокировку.


Дальше сам себе отвечаю:
Цитата
Вопрос 1: приводит ли присвоение nil глобальным переменным из функции main() (то есть не главного потока) к неопределенным ситуациям?
Нет, присвоение делается кодом Lua, то есть под блокировкой.

Цитата
Вопрос 2: имеется ввиду, что другие потоки блокируются при попытке вызвать (ту же или какую-то другую) потокобезопасную функцию (классическая critical section)? Или же вообще все потоки волшебным образом останавливаются, ну, так, "на всякий случай"? :)
Блокируется интерпретатор Lua в другом потоке при попытке выполнть следующую инструкцию (либо "Си" функция при попытке вызвать Lua C API).

Цитата
Вопрос 3: как вообще тогда использовать table.ssort()?
По назначению - никак.  Но можно использовать для создания "критических секций", то есть вызова произвольних функций при удерживаемой блокировке:
Код
table.ssort({ 0, 0 }, function()
   -- код здесь выполняется под блокировкой
   return true
end)
Только не забываем, что table.ssort() "виснет", если в функции сравнения возникает ошибка (проверял на Quik 7.14.1.7):
Код
function main()
  table.ssort({ 1, "a" })
end
Поэтому в нужных местах используем pcall().

Цитата
Вопрос 4: в примере выше, что произойдет, если один поток изменит таблицу потокобезопасной функцией как раз в то время, когда другой поток находится где-то в дебрях интерпретатора Lua (внутри ipairs(), t[key], или еще где)? Что именно сделано, чтобы все не "взорвалось"? Или "так делать нельзя"?
ipairs() является функцией стандартной библиотеки Lua и не является потокобезопасной - "так делать нельзя".  Lua код t[key] будет выполнен с удержанием той же блокировки, которая используется в потокобезопасной функции, так что выполнится или до, или после такой функции, но никак не одновременно - все будет безопасно.

Цитата
Вопрос 5: а какие "сообщения" можно посылать таким образом? Абсолютно любые "типы" Lua, или есть ограничения? Этот вопрос связан со следующим:
Абсолютно любые типы Lua.

Цитата
Вопрос 6: а как потоки дружат со сборщиком мусора (garbage collector)? Он один на все потоки, или по одному на каждый? Если один, то в каком потоке выполняется, и что именно сделано, чтобы GC в одном потоке не "взорвал" другой работающий поток, и не "взорвался" сам, считая свободной память, которую другой поток как раз начинает использовать)? Если же GC у каждого потока свой, то в случае message passing как в примере выше как именно передается ownership (то есть чей GC отвечает за рекламацию сообщения)?
GC может запуститься в любом потоке, но при удерживаемой блокировке.  Поскольку настоящей параллельной работы интерпретаторов Lua не бывает, то GC работает точно так же, как и в случае обычных сопроцедур Lua (coroutines) в одном потоке: никаких раздельных ownership нет, GC проводит рекламацию объектов в environments обоих потоков (а второй интерпретатор ждет освобождения блокировки) - все безопасно.
Потоковая безопасность в QLua - документация и реальность
 
Все, кажется я смог перевести слова Михаила: в основном потоке вы вызываете
Код
lua_State *L = lua_newstate(...);
lua_State *T = lua_newthread(L);

L и T разделяют глобальные объекты, и доступ к этим объектам вы лочите.  Дальше в основном потоке используете L, а в потоке main() используете T.  Да, пишут, что такой вариант вполне рабочий.  Ну и, видимо, как-то сериализуете запуск GC, чтобы не работал параллельно с другим потоком и не лез в его данные.  Если не трудно, то все же скажите, как, и как именно "блокируются" потоки в table.sconcat() - это все хочется знать, чтобы понимать, как именно лучше писать Lua код, какая операция сколько стоит...  Теперь, например, стало ясно, что в начале скрипта нужно писать
Код
local string, table, pairs, ipairs = string, table, pairs, ipairs
и т.д., чтобы не лочить global environment при каждом доступе к string.something() и т.д.
Потоковая безопасность в QLua - документация и реальность
 
Цитата
Sergey Gorokhov написал:
Цитата
kroki   написал:
По-моему в ваших словах противоречие:
По моему это Ваш код противоречит Вашим словам.
Еще раз, согласно Вашему коду никакого одновременного присвоения нет. А значит вопрос совершенно никак не связан с цитатой из документации.
В документации не идет речи о присвоении, есть выражение "одновременная работа".  То есть не только запись, но и чтение.  Потоки не могут не читать global environment: всякий раз, когда вы пишите table.something() или string.something(), вы читаете значения _G["table"] и _G["string"].  Давайте еще раз:
  1. Является ли присвоение переменной значения nil операцией изменения таблицы, в которой переменная хранится? - Да, в Lua присвоение _G["var"] = nil это операция удаления из таблицы.
  2. Обращаются ли разные потоки к таблице глобальных переменных? - Да, как сказано выше, этого не избежать.
  3. Можно ли одновременно менять таблицу из одного потока и читать из другого без блокировок? - Нет, согласно документации (и здравому смыслу) нужно использовать потокобезопасные функции.
  4. Используются ли потокобезопасные функции при обращении к глобальным переменным? - Нет, к глобальным переменным обращаются напрямую.
Отсюда вывод: присваивать глобальным переменным значение nil из потока нельзя.  Не знаю, почему сей логический вывод дается с таким трудом.

Цитата
Цитата
kroki   написал:
Попробую угадать:
Не надо искать скрытый смысл там где его просто нет.
Фраза "блокирует выполнение кода в другом потоке" означает ровно то что означает без скрытого смысла.
И тут нет никакого сокрального знания.
Возможно играет роль разница в кругозорах.  Вы видите ровно один вариант интерпретации и для вас все однозначно.  Я вижу несколько вариантов, начиная с того наблюдения, что словом "блокировка" на русский язык переводят английские термины как blocking, так и locking (и это совершенно разные концепции), и заканчивая семантикой русского языка: если вас спрашивают, "вы курите?", то это может означать как "прямо сейчас", так и "вообще по жизни".  Поэтому "блокирует выполнение кода в другом потоке" может означать как захват какой-то блокироки (Lock), так и блокирование (остановку) в каком-то месте кода (Blocking); как "прямо сейчас", так и "в момент, когда выполнение потока дойдет до критической секции".   Как видите, интерпретаций несколько, и мне хотелось понять, что именно имеется ввиду.  Я понял, что вы сделали все, что могли, и ответа не дадите (а я и не утверждаю, что обязаны - вы уже дали достаточно других ответов).

Цитата
Цитата
kroki   написал:
Вопрос 3: какой смысл в потокобезопасной сортировке, если ее результат (то есть упорядоченный массив) увидеть (то есть получить к нему доступ) никак нельзя?  Если увидеть можно, то, пожалуйста, приведите пример кода.  Я утверждаю, что после выхода из table.ssort() и перед любым другим действием другой поток может что-то вставить в массив с помощью table.sinsert(), и, таким образом, нарушить его упорядоченность - а тогда зачем было вообще упорядочивать?
Цель функции ssort выполнить сортировку таблицы не боясь что во время сортировки в таблице изменятся данные.
Ровно это функция и делает. Если не видите смысла использовать эту функцию то просто не используйте и все.
Никто не заставляет.
- Доктор, когда я вот так делаю, у меня болит.
- А вы так не делайте.

Цитата
Если нужно заблокировать остальные потоки во время вывода таблицы, то при чем тут функция ssort?
Речь была не о выводе конкретно, а о любом доступе к "упорядоченной" таблице.  Документация утверждает, что функция упорядочивает, но проверить и воспользоваться этим никак нельзя: если другие потоки не блокировать, то после завершения table.ssort() перед любой другой операцией упорядоченность может быть нарушена.  Если же другие потоки блокировать, но зачем вообще "потокобезопасная сортировка" (не говоря о том, что блокировать в QLua API просто нечем)?  Опять же, я прямо теряюсь в догадках, почему сей очевидный факт не находит у вас понимания.

Цитата
Цитата
kroki   написал:
Если GC запускаются в каждом потоке свой, то как они не "наступают друг другу на пятки", и не конфликтуют с выполнением другого потока?
В этом вопросе лучше и правильней обратиться к истокам. garbage collector не является нашей разработкой.
Цитата
kroki   написал:
Вопрос 6: как исключается ситуация коллизии между GC в разных потоках, запущенными одновременно?  Не могут два разных GC одновременно начать удалять один и тот же объект?
В нашем ПО, это никак не исключается. Все работает так как это реализовали авторы Lua.
Можете проверить поведение функции поставив ряд экспериментов.
Ага, моменты истины!  Если ваши утверждения верны (а я надеюсь, что нет), то вся реализация QLua - абсолютно небезопасна относительно потоков.  С ваших слов, вы взяли реализацию Lua, которая к многопоточности абсолютно не готова (там есть объект lua_thread, но это coroutines - совершенно другой зверь), и запустили в разных потоках два интерпретатора на одном environment (то есть используя один объект lua_State - ммм, значит GC все же один) - безо всякой синхронизации.  Гляньте второй пост здесь - пишет как раз автор Lua, и говорит как раз то, что даже если environments разные, шарить один lua_State между потоками нельзя...

Ммм, последняя реплика Михаила содержит какой-то намек, но из поста автора Lua следует, что лочить только доступ к глобальным переменным - не достаточно, а если лочить все, что необходимо лочить, то это "makes all Lua code serialized and with a heavy lock/unlock overhead".

В общем я понял, что дело мутное.  Но спасибо, Сергей!
Функция criticalSection() для выполнения произвольного кода в потокобезопасном режиме
 
В примере, конечно, не
Код
t1.insert(v1)
а
Код
table.insert(t1, v1)
Но вы это поняли...
Функция criticalSection() для выполнения произвольного кода в потокобезопасном режиме
 
Пока в соседней ветке пытаюсь выяснить, как использовать table.ssort() по назначению, придумал ей применение во благо: функция criticalSection() ниже выполняет переданную ей функцию, предварительно заблокировав другой поток.

Важно: в своей функции, которую вы передаете в criticalSection(), ни в коем случае не вызывайте потокобезопасные функции, а используйте их стандартные версии.  Ваша функция может "бросать исключения" - финальный вариант это поддерживает.

Сама функция ниже, пример использования:
Код
local t1, t2 = {}, {}
local function insert(v1, v2)
   if v1 > v2 then error("v1 > v2") end

   t1.insert(v1)
   t2.insert(v2)

   return v1 + v2, v1 - v2
end

-- далее где-то в разных потоках "атомарно" вставляем оба значения
local r1, r2 = criticalSection(insert, a1, a2)
Функция (вам нужны первые две строки и финальный (последний) вариант, остальные в целях объяснения):
Код
-- В Lua 5.1 нет table.unpack, но есть unpack.
if not table.unpack then table.unpack = unpack end


-- Понятная версия.  На входе функция и ее аргументы (ноль или более),
-- переданная функция вызывается со своими аргументами под
-- блокировкой, на выходе все ее возвращаемые значения.
local function criticalSection(func, ...)
   -- Запаковываем аргументы для переданной функции в table.
   local args = {...}
   local res
   -- Функция "сравнения" для table.ssort() (передаваемые элементы
   -- упорядочиваемого массива игнорируются).
   local less = function()
      -- Распаковываем аргументы обратно в список, вызываем с ними
      -- переданную функцию, запаковываем результаты в table.
      res = { func(table.unpack(args)) }
      -- Говорим, что первый элемент упорядочиваемого массива меньше
      -- второго, то есть менять элементы местами не нужно.
      return true
   end
   -- "Упорядочиваем" массив из двух элементов (то есть наша функция
   -- "сравнения" вызовется ровно один раз), при этом во время вызова
   -- удерживается блокировка.
   table.ssort({ 0, 0 }, less)
   -- Распаковываем результаты обратно в список и возвращаем его.
   return table.unpack(res)
end


-- Слегка оптимизированная версия: массив { 0, 0 } создается только один
-- раз, функции unpack и ssort ищутся в модуле table также только один раз.
local criticalSection = (function()
   local unpack, ssort = table.unpack, table.ssort
   local array = { 0, 0 }
   return function(func, ...)
      local args = {...}
      local res
      local less = function()
         res = { func(unpack(args)) }
         return true
      end
      ssort(array, less)
      return unpack(res)
   end
end)()


-- Финальная версия: если внутри функции, переданной пользователем,
-- возникнет ошибка, то table.ssort() вылетит, не сняв блокировку (по
-- крайней мере так происходит в моей версии 7.5.0.72 - знаю, что
-- старая, но других брокер не дает).  Ниже код, решающий проблему -
-- на всякий случай именно его и следует использовать.
local criticalSection = (function()
   local unpack, ssort = table.unpack, table.ssort
   local array = { 0, 0 }
   local function pack(ok, ...) return ok, {...} end
   return function(func, ...)
      local args = {...}
      local ok, res
      local less = function()
         ok, res = pack(pcall(func, unpack(args)))
         return true
      end
      ssort(array, less)
      if ok then
         return unpack(res)
      else
         error(unpack(res), 0)
      end
   end
end)()
Потоковая безопасность в QLua - документация и реальность
 
Цитата
Sergey Gorokhov написал:
Цитата
kroki   написал:
Вопрос 1: приводит ли присвоение nil глобальным переменным из функции main() (то есть не главного потока) к неопределенным ситуациям?
А как связано простое присвоение nil с одновременной работой из функций обратного вызова скрипта и функции main()?
В момент одного из таких присваиваний (то есть удалений) реализация Lua может решить аллоцировать для таблицы меньший кусок памяти, перехэшировать, и пр.  Если во время такого передела другой поток обратится к какой-то глобальной переменной, то...
Цитата
Вы обыгрываете совершенно разные сценарии, при этом считаете что это одно и тоже.
Вот если бы Вы из разных потоков одновременно присваивали nil, тогда да, согласно документации это может привести к неопределенным последствиям.
По-моему в ваших словах противоречие: вы же не можете знать, делаете ли вы "простое присваивание nil" (которое по-вашему не связано с функцией main()), или "из разных потоков одновременно присваивали nil", которое делать нельзя - вдруг присваивание делается изнутри какой-то функции, которую даже не вы написали.  Отсюда:

Ответ 1: присваивать глобальным переменным nil нельзя.  Динамически создавать новые глобальные имена также нельзя.  Так?

Цитата
Цитата
kroki   написал:
Читаем документацию дальше: Выполнение потокобезопасной функции блокирует выполнение кода в другом потоке до окончания работы функции.
на наш взгляд фраза "блокирует выполнение кода в другом потоке" вполне понятна и не требует уточнений.
И не делиться сакральным знанием - ваша политика? :)  Но я понял: другой поток останавливается, даже если он не пытается вызвать потокобезопасную функцию.  Попробую угадать:

Ответ 2: в интерпретатор Lua (видимо, перед выполнением следующей инструкции байт-кода) встроена проверка флага, что другой поток вызывает потокобезопасную функцию.  Увидев флаг, интерпретатор останавливается и ждет, пока другой поток выйдет из функции.  Конечно, можtт получиться так, что поток уже выполняет код GC, который написан на C и в Lua долго не вернется, флага не увидит.  Но это превращает вопрос 2 в вопрос 6, так что этот вопрос снимаю.

Цитата
Цитата
kroki   написал:
Как только table.ssort() закончит работу другой поток может добавить новые данные - и у нас опять потенциально не упорядоченная таблица, которую мы и напечатаем.
Вопрос 3: как вообще тогда использовать table.ssort()?
А чего Вы ожидали? что оно само будет где-то параллельно сортироваться после окончания ssort? Нет такого не будет.
Не уверен, поняли ли вы вопрос (я ожидал только его понимания, больше ничего).  Перефразирую:

Вопрос 3: какой смысл в потокобезопасной сортировке, если ее результат (то есть упорядоченный массив) увидеть (то есть получить к нему доступ) никак нельзя?  Если увидеть можно, то, пожалуйста, приведите пример кода.  Я утверждаю, что после выхода из table.ssort() и перед любым другим действием другой поток может что-то вставить в массив с помощью table.sinsert(), и, таким образом, нарушить его упорядоченность - а тогда зачем было вообще упорядочивать?

Цитата
Цитата
kroki   написал:
Вопрос 4: в примере выше, что произойдет, если один поток изменит таблицу потокобезопасной функцией как раз в то время, когда другой поток находится где-то в дебрях интерпретатора Lua (внутри ipairs(), t[key], или еще где)?  Что именно сделано, чтобы все не "взорвалось"?  Или "так делать нельзя"?
Вы же сами выше процетировали:
Цитата
kroki   написал:
Выполнение потокобезопасной функции  блокирует выполнение кода в другом потоке  до окончания работы функции.
И там же я сказал, что смысл фразы мне не вполне ясен.  Если ответ 2 я понял правильно, то да, вопрос 4 снимается.

Цитата
Цитата
kroki   написал:
Вопрос 5: а какие "сообщения" можно посылать таким образом?  Абсолютно любые "типы" Lua, или есть ограничения?  Этот вопрос связан со следующим:
Если вопрос в том, что можно вставить в таблицу, то это совсем никак не связано с вопросом из п.6.
И, следовательно, вопрос не в том, что можно вставить в таблицу ;).  Подробнее ниже.

Цитата
Цитата
kroki   написал:
Вопрос 6: а как потоки дружат со сборщиком мусора (garbage collector)?  Он один на все потоки, или по одному на каждый?  Если один, то в каком потоке выполняется, и что именно сделано, чтобы GC в одном потоке не "взорвал" другой работающий поток, и не "взорвался" сам, считая свободной память, которую другой поток как раз начинает использовать)?  Если же GC у каждого потока свой, то в случае message passing как в примере выше как именно передается ownership (то есть чей GC отвечает за рекламацию сообщения)?
Все LUA функции выполняются в том потоке в котором вызваны и не важно garbage collector это или любая другая функция.
Потокобезопасные функции отличаются только тем что блокируют другой поток пока сами выполняются и опять же в том потоке где запущены. И garbage collector к ним не относится.
Раз из разных потоков можно обращаться к одним и тем же глобальным именам, значит разные потоки работают с одним и тем же global environment (точнее, могла быть и копия, но нет, проверял).  Если GC запускаются в каждом потоке свой, то как они не "наступают друг другу на пятки", и не конфликтуют с выполнением другого потока?  Например, в одном потоке запустился GC, но работает-то он со всей памятью (раз environment общий).  Если никакой синхронизции между потоками нет, то события "объект больше не используется" и "на объект больше никто не ссылается" в общем случае разные потоки могут обозревать в разном порядке, как из-за out of order execution, так и в банальных случаях, когда два GC в разных потоках одновременно увидят, что такая-то таблица более не используется, и одновременно попытаются ее освободить - возможно возникнет бо-бо :).  Вопрос 5 вырос из предположения, что разные GC работают каждый на своих данных, которыми он "владеет", и коллизий не бывает.  Я более не думаю, что это так (снимаю вопрос 5), но тогда остается:

Вопрос 6: как исключается ситуация коллизии между GC в разных потоках, запущенными одновременно?  Не могут два разных GC одновременно начать удалять один и тот же объект?
Потоковая безопасность в QLua - документация и реальность
 
Ниже несколько вопросов к поддержке и/или знатокам по теме потоковой безопасности.

Читаем документацию:
Цитата
Одновременная работа с таблицами из функций обратного вызова скрипта и функции main() может приводить к неопределенным ситуациям.
В языке Lua все переменные на самом деле являются ключами таблиц, называемых environments.  Присвоение ключу значения nil удаляет его из таблицы.  То есть, если в коде написано
Код
var = 5  -- глобальная переменная

function main()
   var = nil
end 
то это то же самое, что и
Код
_G["var"] = 5  -- глобальная переменная

function main()
   _G["var"] = nil
end 
(в новых версиях Lua вместо _G теперь _ENV, но не суть).  Видно, что функция main() удаляет ключ var из таблицы _G.
Вопрос 1: приводит ли присвоение nil глобальным переменным из функции main() (то есть не главного потока) к неопределенным ситуациям?

Читаем документацию дальше:
Цитата
Выполнение потокобезопасной функции блокирует выполнение кода в другом потоке до окончания работы функции.
Вопрос 2: имеется ввиду, что другие потоки блокируются при попытке вызвать (ту же или какую-то другую) потокобезопасную функцию (классическая critical section)?  Или же вообще все потоки волшебным образом останавливаются, ну, так, "на всякий случай"? :)

Еще читаем:
Цитата
В таблице представлены стандартные функции Lua и соответствующие им потокобезопасные аналоги:
concat   sconcat
remove  sremove
insert     sinsert
sort       ssort
Про table.sconcat() вопросов нет (ну хоть что-то понял! :)).  Думаем про table.ssort():
Код
local t = {}

function OnSomething(param)  -- какой-то callback
   table.sinsert(t, param)
end

function main()
   table.ssort(t)
   for _, v in ipairs(t) do
      print(v)
   end
end
Как только table.ssort() закончит работу другой поток может добавить новые данные - и у нас опять потенциально не упорядоченная таблица, которую мы и напечатаем.
Вопрос 3: как вообще тогда использовать table.ssort()?

Думаем про table.sinsert() и table.sremove():
Вопрос 4: в примере выше, что произойдет, если один поток изменит таблицу потокобезопасной функцией как раз в то время, когда другой поток находится где-то в дебрях интерпретатора Lua (внутри ipairs(), t[key], или еще где)?  Что именно сделано, чтобы все не "взорвалось"?  Или "так делать нельзя"?

Но предположим, что мы используемтолько потокобезопасные функции и другими способами к таблице не обращаемся, и реализуем классический message passing, то есть очередь сообщений между потоками:
Код
local mqueue = {}

function OnSomething(param)  -- какой-то callback
   while true do
      local msg = table.sremove(mqueue, 1)
      if not msg then break end
      -- обрабатываем сообщение msg (например, меняем логику работы этого callback)
   end
   -- обрабатываем param согласно (возможно новой) логике
end

function main()
   ...
   table.sinsert(<сообщение>)
   ...
end
Вопрос 5: а какие "сообщения" можно посылать таким образом?  Абсолютно любые "типы" Lua, или есть ограничения?  Этот вопрос связан со следующим:

Вопрос 6: а как потоки дружат со сборщиком мусора (garbage collector)?  Он один на все потоки, или по одному на каждый?  Если один, то в каком потоке выполняется, и что именно сделано, чтобы GC в одном потоке не "взорвал" другой работающий поток, и не "взорвался" сам, считая свободной память, которую другой поток как раз начинает использовать)?  Если же GC у каждого потока свой, то в случае message passing как в примере выше как именно передается ownership (то есть чей GC отвечает за рекламацию сообщения)?

Очень надеюсь получить ответы на все вопросы (и в особенности на 6-й) - хочется проникнуться доверием к реализации, управляющей моими активами. :)

Заранее спасибо!
Страницы: 1
Наверх