Подвешивание info.exe через механизм межпоточного локинга

Страницы: 1
RSS
Подвешивание info.exe через механизм межпоточного локинга
 
Цитата
tid = 0

events = {}
mp_counter = 0
qp_counter = 0

-- Predicates to be called from two threads
-- To hang Quik we will try to collide QLUA functions
-- Risk factors: we're using heavily CriticalSection (through ssort) and the events are fast enough

-- Predicate for script's dedicated thread
function main_predicate(a, b)
  mp_counter = mp_counter + 1;
  if ((mp_counter % 7) == 0) then Clear(tid) end
  SetWindowCaption(tid, "Just called script thread predicate " .. mp_counter .. " times")
  local n = InsertRow(tid, -1)
  SetCell(tid, n, 1, "Compared " .. a .. " and " .. b .. " with script thread predicate", 0);
  return b > a
end

-- Predicate for Quik's main thread
function quik_predicate(a, b)
  qp_counter = qp_counter + 1;
  if ((qp_counter % 13) == 0) then Clear(tid) end
  local n = InsertRow(tid, -1)
  SetCell(tid, n, 1, "Compared " .. a .. " and " .. b .. " with main thread predicate", 0);
  SetWindowCaption(tid, "Just called quik thread predicate " .. qp_counter .. " times")
  return a < b
end

function main()
  Subscribe_Level_II_Quotes("QJSIM", "LKOH");
  Subscribe_Level_II_Quotes("QJSIM", "SBER");
  Subscribe_Level_II_Quotes("QJSIM", "GMKN");
  Subscribe_Level_II_Quotes("QJSIM", "SBERP");
  Subscribe_Level_II_Quotes("QJSIM", "MGNT");
  Subscribe_Level_II_Quotes("QJSIM", "ROSN");
  Subscribe_Level_II_Quotes("QJSIM", "AFLT");
  Subscribe_Level_II_Quotes("QJSIM", "BANE");
  Subscribe_Level_II_Quotes("QJSIM", "MTLR");
  Subscribe_Level_II_Quotes("QJSIM", "BANEP");

  tid = AllocTable()
  AddColumn(tid, 1, "Event", true, QTABLE_STRING_TYPE, 80)
  CreateWindow(tid)
  local cycles = 0;
  while true do
     sleep(1)
     cycles = cycles + 1;
     if (cycles == 100) then
        table.ssort(events, main_predicate)
        if (table.getn(events) > 0) then
           local n = InsertRow(tid, -1)
           SetCell(tid, n, 1, "First item after sorting in main is: " .. events[1], 0)
        end
     end
  end
end

function OnQuote(class_code, sec_code)
  local q = getQuoteLevel2(class_code, sec_code)
  if ((q ~= nil) and (tonumber(q.bid_count) > 0)) then
     table.sinsert(events, q.bid[1].price)
     table.ssort(events, quik_predicate)
     if (table.getn(events) > 0) then
        local n = InsertRow(tid, -1)
        SetCell(tid, n, 1, "First item after sorting in main is: " .. events[1], 0)
     end
  end
end
В течение первой пары минут работы скрипт подвесит терминал. Придумывалось, как тестовый скрипт для иллюстрации этой проблемы (https://forum.quik.ru/forum10/topic3206/) на чистом Lua, но может быть связан и просто со слишком частым main.
 
Исправление форматирования:
Код
tid = 0

events = {}
mp_counter = 0
qp_counter = 0

-- Predicates to be called from two threads
-- To hang Quik we will try to collide QLUA functions
-- Risk factors: we're using heavily CriticalSection (through ssort) and the events are fast enough

-- Predicate for script's dedicated thread
function main_predicate(a, b)
   mp_counter = mp_counter + 1;
   if ((mp_counter % 7) == 0) then Clear(tid) end
   SetWindowCaption(tid, "Just called script thread predicate " .. mp_counter .. " times")
   local n = InsertRow(tid, -1)
   SetCell(tid, n, 1, "Compared " .. a .. " and " .. b .. " with script thread predicate", 0);
   return b > a
end

-- Predicate for Quik's main thread
function quik_predicate(a, b)
   qp_counter = qp_counter + 1;
   if ((qp_counter % 13) == 0) then Clear(tid) end
   local n = InsertRow(tid, -1)
   SetCell(tid, n, 1, "Compared " .. a .. " and " .. b .. " with main thread predicate", 0);
   SetWindowCaption(tid, "Just called quik thread predicate " .. qp_counter .. " times")
   return a < b
end

function main()
   Subscribe_Level_II_Quotes("QJSIM", "LKOH");
   Subscribe_Level_II_Quotes("QJSIM", "SBER");
   Subscribe_Level_II_Quotes("QJSIM", "GMKN");
   Subscribe_Level_II_Quotes("QJSIM", "SBERP");
   Subscribe_Level_II_Quotes("QJSIM", "MGNT");
   Subscribe_Level_II_Quotes("QJSIM", "ROSN");
   Subscribe_Level_II_Quotes("QJSIM", "AFLT");
   Subscribe_Level_II_Quotes("QJSIM", "BANE");
   Subscribe_Level_II_Quotes("QJSIM", "MTLR");
   Subscribe_Level_II_Quotes("QJSIM", "BANEP");

   tid = AllocTable()
   AddColumn(tid, 1, "Event", true, QTABLE_STRING_TYPE, 80)
   CreateWindow(tid)
   local cycles = 0;
   while true do
      sleep(1)
      cycles = cycles + 1;
      if (cycles == 100) then
         table.ssort(events, main_predicate)
         if (table.getn(events) > 0) then
            local n = InsertRow(tid, -1)
            SetCell(tid, n, 1, "First item after sorting in main is: " .. events[1], 0)
         end
      end
   end
end

function OnQuote(class_code, sec_code)
   local q = getQuoteLevel2(class_code, sec_code)
   if ((q ~= nil) and (tonumber(q.bid_count) > 0)) then
      table.sinsert(events, q.bid[1].price)
      table.ssort(events, quik_predicate)
      if (table.getn(events) > 0) then
         local n = InsertRow(tid, -1)
         SetCell(tid, n, 1, "First item after sorting in main is: " .. events[1], 0)
      end
   end
end


 
El El,
В заголовке times уже +30000 пока полет нормальный.
А у Вас при каком значении зависает терминал?
 
Заметил, что не сбрасываю счетчик cycles, соответственно видимо тут столкновения происходят между вызовами на основном потоке Quik
 
Цитата
El El,  В заголовке times уже +30000 пока полет нормальный. А у Вас при каком значении зависает терминал?
По-разному, попробуйте попосылать ему UI события (покликать на окно мышкой, подвигать окно), подвиснет гораздо быстрее, иногда сразу.
 
El El,
Воспроизвели.
Проблема изучается. Постараемся в ближайшее время дать ответ.
 
Дополнительно: оно иногда отвисает, если подождать минут 10 после повисания, после чего UI Квика нормален, но скрипт как будто останавливается. В статусе по иконке в списке скриптов тем не менее - продолжение исполнения (зеленая стрелочка).
 
Добрый день.
Эксперимернты показали, что взаимных блокировок (deadlock) при работе скрипта не происходит. Просто основной поток фронта очень загружен и  не откликается на действия пользователей. Потокобезопаные функции библиотеки table устроены так, что на время выполнения полностью блокируют все остальные потоки для данной виртуальной машины Lua. Учитывая что таблица events в Вашем примере постоянно растет, то и время работы функций sinsert и ssort все время увеличивается.
 
Цитата
Michael Bulychev написал:
Добрый день.
Эксперимернты показали, что взаимных блокировок (deadlock) при работе скрипта не происходит. Просто основной поток фронта очень загружен и  не откликается на действия пользователей. Потокобезопаные функции библиотеки table устроены так, что на время выполнения полностью блокируют все остальные потоки для данной виртуальной машины Lua. Учитывая что таблица events в Вашем примере постоянно растет, то и время работы функций sinsert и ssort все время увеличивается.
У меня подвисает даже если поддерживать таблицу events размером не более 2 элемента в OnQuote


Модифицированный скрипт:
Код
tid = 0

events = {}
mp_counter = 0
qp_counter = 0

-- Predicates to be called from two threads
-- To hang Quik we will try to collide QLUA functions
-- Risk factors: we're using heavily CriticalSection (through ssort) and the events are fast enough

-- Predicate for script's dedicated thread
function main_predicate(a, b)
   mp_counter = mp_counter + 1;
   if ((mp_counter % 7) == 0) then Clear(tid) end
   SetWindowCaption(tid, "Just called script thread predicate " .. mp_counter .. " times")
   local n = InsertRow(tid, -1)
   SetCell(tid, n, 1, "Compared " .. a .. " and " .. b .. " with script thread predicate", 0);
   return b > a
end

-- Predicate for Quik's main thread
function quik_predicate(a, b)
   qp_counter = qp_counter + 1;
   if ((qp_counter % 13) == 0) then Clear(tid) end
   local n = InsertRow(tid, -1)
   SetCell(tid, n, 1, "Compared " .. a .. " and " .. b .. " with main thread predicate", 0);
   SetWindowCaption(tid, "Just called quik thread predicate " .. qp_counter .. " times")
   return a < b
end

function main()
   Subscribe_Level_II_Quotes("QJSIM", "LKOH");
   Subscribe_Level_II_Quotes("QJSIM", "SBER");
   Subscribe_Level_II_Quotes("QJSIM", "GMKN");
   Subscribe_Level_II_Quotes("QJSIM", "SBERP");
   Subscribe_Level_II_Quotes("QJSIM", "MGNT");
   Subscribe_Level_II_Quotes("QJSIM", "ROSN");
   Subscribe_Level_II_Quotes("QJSIM", "AFLT");
   Subscribe_Level_II_Quotes("QJSIM", "BANE");
   Subscribe_Level_II_Quotes("QJSIM", "MTLR");
   Subscribe_Level_II_Quotes("QJSIM", "BANEP");

   tid = AllocTable()
   AddColumn(tid, 1, "Event", true, QTABLE_STRING_TYPE, 80)
   CreateWindow(tid)
   local cycles = 0;
   while true do
      sleep(1)
      cycles = cycles + 1;
      if (cycles == 100) then
         table.ssort(events, main_predicate)
         if (table.getn(events) > 0) then
            local n = InsertRow(tid, -1)
            SetCell(tid, n, 1, "First item after sorting in main is: " .. events[1], 0)
         end
         cycles = 0
      end
   end
end

function OnQuote(class_code, sec_code)
   local q = getQuoteLevel2(class_code, sec_code)
   if ((q ~= nil) and (tonumber(q.bid_count) > 0)) then
      if (table.getn(events) > 2) then events = {} end
      table.sinsert(events, q.bid[1].price)
      table.ssort(events, quik_predicate)
      if (table.getn(events) > 0) then
         local n = InsertRow(tid, -1)
         SetCell(tid, n, 1, "First item after sorting in main is: " .. events[1], 0)
      end
   end
end

 
Вообще, условие возникновение проблемы гораздо более простое, чем в скрипте. Нужно, чтобы начал исполняться обработчик (любой: OnQuote, OnTrade), но до того как он закончил исполнение, проснулся бы другой тред и вызвал бы "плохую функцию", например Clear. Если "плохая" функция начнет исполнение до того, как закончит исполнение пользовательский обработчик, это достаточное условие чтоб оно подвисло. Совсем не нужно мучать ssort и прочее. Просто это sleep и ssort-подбные функции - единственные, чем можно управлять в Qlua потоками. А мне нужно было что-то, чем управлять потоками, чтоб в соответствии с "регламентом" обратиться на форуме и привести пример проблемы со скриптом на Lua. Я не мог сказать "усыпляйте мейн тред скрипта, а в конце любого обработчика пробуждайте, так, чтоб сразу вызвался Clear в мейне скрипта". Но изначально с проблемой я столкнулся сделав именно это.
 
Чтобы было проще, я написал логирование каждого вызова к Qlua функции. Логинг в stdout, т.к. в такой ситуации на дебаг стринги через сам Qlua, который пытаемся дебагить, полагаться нельзя, поэтому чтоб получить лог, нужно фронт пускать через терминал или с перенаправлением stdout: info.exe > log.txt
Этот вариант у меня подвисает минуты через 2 после старта.

Тестовый скрипт:
Код
tid = 0

events = {}
mp_counter = 0
qp_counter = 0

-- Predicates to be called from two threads
-- To hang Quik we will try to collide QLUA functions
-- Risk factors: we're using heavily CriticalSection (through ssort) and the events are fast enough

-- Predicate for script's dedicated thread
function script_predicate(a, b)
   print("SCRIPT: script_predicate entry")
   mp_counter = mp_counter + 1;
   if ((mp_counter % 7) == 0) then
      print("SCRIPT: Calling Clear")
      Clear(tid)
      print("SCRIPT: Returned from Clear")
   end
   print("SCRIPT: Calling SetWindowCaption")
   SetWindowCaption(tid, "Just called script thread predicate " .. mp_counter .. " times")
   print("SCRIPT: Returned from SetWindowCaption")
   print("SCRIPT: Calling InsertRow")
   local n = InsertRow(tid, -1)
   print("SCRIPT: Returned from InsertRow")
   print("SCRIPT: Calling SetCell")
   SetCell(tid, n, 1, "Compared " .. a .. " and " .. b .. " with script thread predicate", 0);
   print("SCRIPT: Returned from SetCell")
   print("SCRIPT: script_predicate end")
   return b > a
end

-- Predicate for Quik's main thread
function main_predicate(a, b)
   print("MAIN: main_predicate entry")
   qp_counter = qp_counter + 1;
   if ((qp_counter % 7) == 0) then
      print("MAIN: Calling Clear")
      Clear(tid)
      print("MAIN: Returned from Clear")
   end
   SetWindowCaption(tid, "Just called main thread predicate " .. cp_counter .. " times")
   print("MAIN: Returned from SetWindowCaption")
   print("MAIN: Calling InsertRow")
   local n = InsertRow(tid, -1)
   print("MAIN: Returned from InsertRow")
   print("MAIN: Calling SetCell")
   SetCell(tid, n, 1, "Compared " .. a .. " and " .. b .. " with main thread predicate", 0);
   print("MAIN: Returned from SetCell")
   print("MAIN: main_predicate end")
   return a < b
end

function main()
   -- Turn off buffering to stdout
   io.stdout:setvbuf("no")
   
   Subscribe_Level_II_Quotes("QJSIM", "LKOH");
   Subscribe_Level_II_Quotes("QJSIM", "SBER");
   Subscribe_Level_II_Quotes("QJSIM", "GMKN");
   Subscribe_Level_II_Quotes("QJSIM", "SBERP");
   Subscribe_Level_II_Quotes("QJSIM", "MGNT");
   Subscribe_Level_II_Quotes("QJSIM", "ROSN");
   Subscribe_Level_II_Quotes("QJSIM", "AFLT");
   Subscribe_Level_II_Quotes("QJSIM", "BANE");
   Subscribe_Level_II_Quotes("QJSIM", "MTLR");
   Subscribe_Level_II_Quotes("QJSIM", "BANEP");

   tid = AllocTable()
   AddColumn(tid, 1, "Event", true, QTABLE_STRING_TYPE, 80)
   CreateWindow(tid)
   local cycles = 0;
   while true do
      sleep(1)
      cycles = cycles + 1;
      if (cycles == 100) then
         print("SCRIPT: Calling ssort")
         table.ssort(events, script_predicate)
         print("SCRIPT: Returned from ssort")
         if (table.getn(events) > 0) then
            print("SCRIPT: Calling InsertRow")
            local n = InsertRow(tid, -1)
            print("SCRIPT: Returned from InsertRow")
            print("SCRIPT: Calling SetCell")
            SetCell(tid, n, 1, "First item after sorting in main is: " .. events[1], 0)
            print("SCRIPT: Returned from SetCell")
         end
         cycles = 0
      end
   end
end

function OnQuote(class_code, sec_code)
   print("MAIN: OnQuote entry")
   local q = getQuoteLevel2(class_code, sec_code)
   if ((q ~= nil) and (tonumber(q.bid_count) > 0)) then
      print("SCRIPT: Calling getn")
      if (table.getn(events) > 8) then events = {} end
      print("SCRIPT: Returned from getn")
      print("SCRIPT: Calling sinsert")
      table.sinsert(events, q.bid[1].price)
      print("SCRIPT: Returned from sinsert")
      print("SCRIPT: Calling ssort")
      table.ssort(events, quik_predicate)
      print("SCRIPT: Returned from ssort")
      if (table.getn(events) > 0) then
         print("SCRIPT: Calling InsertRow")
         local n = InsertRow(tid, -1)
         print("SCRIPT: Returned from InsertRow")
         print("SCRIPT: Calling SetCell")
         SetCell(tid, n, 1, "First item after sorting in main is: " .. events[1], 0)
         print("SCRIPT: Returned from SetCell")
      end
   end
   print("MAIN: OnQuote end")
end

В момент подвисания у меня может быть, например, такой лог:
Код
...
SCRIPT: Calling SetCell
SCRIPT: Returned from SetCell
SCRIPT: script_predicate end
SCRIPT: script_predicate entry
SCRIPT: Calling Clear
SCRIPT: Returned from Clear
SCRIPT: Calling SetWindowCaption
SCRIPT: Returned from SetWindowCaption
SCRIPT: Calling InsertRow



Обратите внимание, что подвисает на InsertRow, когда мы заведомо находимся внутри своей критической секции (внутри предиката к ssort), то есть подвисает не потому, что мы ждем, пока нам передадут управление в рамках занятой кем-то еще критической сессии, используемой в ssort. Подвисания происходят всегда внутри "плохих" функций.
 
Исправление строк в тексте сообщений в OnQuote:
Код
tid = 0

events = {}
mp_counter = 0
qp_counter = 0

-- Predicates to be called from two threads
-- To hang Quik we will try to collide QLUA functions
-- Risk factors: we're using heavily CriticalSection (through ssort) and the events are fast enough

-- Predicate for script's dedicated thread
function script_predicate(a, b)
   print("SCRIPT: script_predicate entry")
   mp_counter = mp_counter + 1;
   if ((mp_counter % 7) == 0) then
      print("SCRIPT: Calling Clear")
      Clear(tid)
      print("SCRIPT: Returned from Clear")
   end
   print("SCRIPT: Calling SetWindowCaption")
   SetWindowCaption(tid, "Just called script thread predicate " .. mp_counter .. " times")
   print("SCRIPT: Returned from SetWindowCaption")
   print("SCRIPT: Calling InsertRow")
   local n = InsertRow(tid, -1)
   print("SCRIPT: Returned from InsertRow")
   print("SCRIPT: Calling SetCell")
   SetCell(tid, n, 1, "Compared " .. a .. " and " .. b .. " with script thread predicate", 0);
   print("SCRIPT: Returned from SetCell")
   print("SCRIPT: script_predicate end")
   return b > a
end

-- Predicate for Quik's main thread
function main_predicate(a, b)
   print("MAIN: main_predicate entry")
   qp_counter = qp_counter + 1;
   if ((qp_counter % 7) == 0) then
      print("MAIN: Calling Clear")
      Clear(tid)
      print("MAIN: Returned from Clear")
   end
   SetWindowCaption(tid, "Just called main thread predicate " .. cp_counter .. " times")
   print("MAIN: Returned from SetWindowCaption")
   print("MAIN: Calling InsertRow")
   local n = InsertRow(tid, -1)
   print("MAIN: Returned from InsertRow")
   print("MAIN: Calling SetCell")
   SetCell(tid, n, 1, "Compared " .. a .. " and " .. b .. " with main thread predicate", 0);
   print("MAIN: Returned from SetCell")
   print("MAIN: main_predicate end")
   return a < b
end

function main()
   -- Turn off buffering to stdout
   io.stdout:setvbuf("no")
   
   Subscribe_Level_II_Quotes("QJSIM", "LKOH");
   Subscribe_Level_II_Quotes("QJSIM", "SBER");
   Subscribe_Level_II_Quotes("QJSIM", "GMKN");
   Subscribe_Level_II_Quotes("QJSIM", "SBERP");
   Subscribe_Level_II_Quotes("QJSIM", "MGNT");
   Subscribe_Level_II_Quotes("QJSIM", "ROSN");
   Subscribe_Level_II_Quotes("QJSIM", "AFLT");
   Subscribe_Level_II_Quotes("QJSIM", "BANE");
   Subscribe_Level_II_Quotes("QJSIM", "MTLR");
   Subscribe_Level_II_Quotes("QJSIM", "BANEP");

   tid = AllocTable()
   AddColumn(tid, 1, "Event", true, QTABLE_STRING_TYPE, 80)
   CreateWindow(tid)
   local cycles = 0;
   while true do
      sleep(1)
      cycles = cycles + 1;
      if (cycles == 100) then
         print("SCRIPT: Calling ssort")
         table.ssort(events, script_predicate)
         print("SCRIPT: Returned from ssort")
         if (table.getn(events) > 0) then
            print("SCRIPT: Calling InsertRow")
            local n = InsertRow(tid, -1)
            print("SCRIPT: Returned from InsertRow")
            print("SCRIPT: Calling SetCell")
            SetCell(tid, n, 1, "First item after sorting in main is: " .. events[1], 0)
            print("SCRIPT: Returned from SetCell")
         end
         cycles = 0
      end
   end
end

function OnQuote(class_code, sec_code)
   print("MAIN: OnQuote entry")
   local q = getQuoteLevel2(class_code, sec_code)
   if ((q ~= nil) and (tonumber(q.bid_count) > 0)) then
      print("MAIN: Calling getn")
      if (table.getn(events) > 8) then events = {} end
      print("MAIN: Returned from getn")
      print("MAIN: Calling sinsert")
      table.sinsert(events, q.bid[1].price)
      print("MAIN: Returned from sinsert")
      print("MAIN: Calling ssort")
      table.ssort(events, quik_predicate)
      print("MAIN: Returned from ssort")
      if (table.getn(events) > 0) then
         print("MAIN: Calling InsertRow")
         local n = InsertRow(tid, -1)
         print("MAIN: Returned from InsertRow")
         print("MAIN: Calling SetCell")
         SetCell(tid, n, 1, "First item after sorting in main is: " .. events[1], 0)
         print("MAIN: Returned from SetCell")
      end
   end
   print("MAIN: OnQuote end")
end

 
Добрый день.
Может будет проще если Вы объясните какую задачу хотите решить?
 
Цитата
Michael Bulychev написал:
Добрый день.
Может будет проще если Вы объясните какую задачу хотите решить?
Изначально моя задача - позволить пользователям моей библиотеки писать потокобезопасный код при написании QLua-плагинов к Quik. Эта задача лежит вне области поддержки с вашей стороны, и я на такую поддержку не претендую.
Решая эту задачу своими методами, я заметил, что при определенных обстоятельствах терминал подвешивается, но глубоко в эту сторону не копал и списывал причины именно на свои методы. Сейчас дошли руки заняться этим вопросом снова, и,
пытаясь найти ошибку в логике своего кода, я пришел к выводу, что проблема не в моем коде (я пробовал разные варианты синхронизации между потоком скрипта и основным потоком). Условия подвисания вырисовалось такое, как я описал выше: терминал подвиснет, если в потоке main произойдет вызов "плохой" функции типа InsertRow или Clear в случае, если на основном потоке не завершилось исполнение хендлера колбека юзерского кода. Вероятно, то же самое происходит если "плохая" функция будет вызвана в коллбеке на основном потоке до того, как завершил работу обработчик коллбека на этом же потоке (этот вариант я не подтвердил твердо экспериментально). Такие условия подвисания должны затрагивать не только меня, как пользователя, пытающегося воспользоваться не поддерживающимся функционалом. Эти подвисания должны случаться и у других пользователей, но, с учетом того, что все они в своем большинстве вставляют в main sleep на сотни миллисекунд и других особенностей типового Qlua скрипта, вероятность такого события просто очень низка. Исходя из этого, я написал скрипт чисто на Quik Lua с использованием строго поддерживающегося функционала, чтобы спровоцировать эту ситуацию с вероятностью достаточно высокой для заметного воспроизведения ошибки. К сожалению, чтоб управлять очередностью исполнения потоков при помощи только поддерживающегося функционала QLua, мне пришлось прибегнуть к "потокобезопасным функциям" (в терминах qlua.chm), используя их неочевидным образом для неочевидных целей, хотя и в соответствии с описанием. Из-за этого по коду может быть не с первого взгляда ясно, при какой ситуации происходит подвисание. Поэтому впоследствии я прокомментировал, что подвисание происходит при указанной очередности событий, хотя относительно нее вам придется поверить (или не поверить) мне на слово, поскольку саму эту очередность я выявил своими неподдерживающимися методами. Просто хотел облегчить вам задачу поиска. Таким образом, моя задача - предотвратить подвисания внутри функций типа Close, InsertRow и др. в обычных QLua скриптах при описанных мной обстоятельствах. Если эта задача будет решена, то с моей стороны это автоматически будет означать и решение моей проблемы потокобезопасных интерфейсов, которая официальной поддержке не подлежит.
 
Michael Bulychev, Может быть проще добавить регистрацию пользовательский  колбеков которые исполняться будут в основном потоке квика? В порядке очереди..
Просто сейчас  тоже думаю как лучше решить похожую задачу(синхронизации потоков), такой небольшой функционал решил бы многие проблемы.

моя задача: в произвольный момент в мейне запрашивается список сделок или заявок. но пока он обрабатываеться могут прийти новые данные (или вообще очистка данных, когда новая сессия пошла). мне нужно сначала обработку завершить. Ставить крит. секцию на все колбеки и обработку данных как то не хочется.
 
Цитата
моя задача: в произвольный момент в мейне запрашивается список сделок или заявок. но пока он обрабатываеться могут прийти новые данные (или вообще очистка данных, когда новая сессия пошла). мне нужно сначала обработку завершить. Ставить крит. секцию на все колбеки и обработку данных как то не хочется.
Конкретно приведенная Вами задача вообще не нуждается в многопоточной обработке. Поэтому нет нужды городить огород с пользовательскими колбеками в потоке Quik.
Мое решение: в событийном колбеке quik полученная информация (таблица) просто вешается в очередь FIFO для дальнейшей обработки в потоке main.
Поток main, когда занят критичной задачей (например, ваша обработка списка накопившихся к моменту старта задачи списка сделок или заявок), не отвлекается на обработку поступающих в очередь событий. Завершив критичную задачу, возобновляет обработку накопившихся в очереди данных, которых ему накидали колбеки Quikа, и, после их обработки, помещает в соответствующий список обработанных (сделок и заявок) для дальнейшего анализа возникающими в потоке main задачами.
 
Алексей,
1) В каждом колбеке будет уникальная ссылка на луа таблицу? Я думал квик изменяет данные а потом дает колбек на обработку пользователю. Чтобы быть на 100% уверенным что правильно вас понял:
Код
//обработка таких данных из мейна
    for (int i = 0; i < q.getNumberOf("orders"); i++)
    {
        //получаем таблицу, паралельно могут приходить колбеки
        lua::table t = q.getItem("orders", i); 
        main t --> (t.id = 1 , t.balance = 1)        
        //паралельно приходит колбек с такими пар-ми
        поток квика --> OnOrder (t.id = 1, t.balance = 0)

        int mybalance = t.balance
        а тут мы обрабатываем остаток(мейн поток). тут я считал что будет паралельный доступ к данным из 2х потоков. разве нет?
        квик записывает balance заявки а мы его в это время считываем. если квик всеже такое переваривает то что я получу при запросе из таблицы t.balance (0 или 1).
        тобишь обновит квик данные хранящиеся в таблице которую я уже получил или нет?
        
    }
 
Делюсь опытом.
Когда я делал синхронизацию потокв в QLUA, то просто написал DLL для решения данной проблемы.
В результате я получил
1) Синхронизацию различных скриптов между собой, т е я синхронизировал майн в различных скриптах.
Это позволило использовать всего по одному вызову каждого колбека QLua вне зависимости от числа роботов (скриптов)
Т е у меня есть один скрит для работы со стаканами один для работы с заявками и  т д
и куда скриптов торговых стратегий(роботов) в которые не требуется вызывать все колбеки QLUA.
Вполне удобное решение, если разработчики КВИК надумают его сделать, то проблемы с синхронизацией потоков и множественными копиями колбеков исчезнут.  
 
Цитата
Антон написал:
1) В каждом колбеке будет уникальная ссылка на луа таблицу?
В каждом колбеке Quik передает Вам lua - таблицу, представляющую собой копию строки таблицы хранения/вывода данных Quikа, которая впервые появилась / изменилась / удалилась.
При каждом Вашем запросе на получение информации о какой-либо строке таблицы хранения/вывода данных Quikа, вы получаете копию (слепок) этой строки (в виде lua - таблицы) на момент запроса.
Под запросом я имею в виду запрос универсального типа (по номеру строки произвольной таблицы) getItem или поисковый запрос (по параметрам строки конкретной таблицы), например, getFuturesHolding, getDepoEx, getOrderByNumber и т.п.
Поэтому, если между Вашим считыванием строки таблицы orders и обработкой полученных данных реальное состояние интересующей Вас заявки изменится (о чем придет OnOrder), уже считанное содержимое никак не поменяется: t.balance будет равно 1. О том, что баланс заявки стал 0 поток main может узнать только лишь еще раз обратившись к таблице orders (getItem или getOrderByNumber) или через реализованный Вами специальный механизм от колбека OnOrder.

Важно понимать, что результат вызова getNumberOf является количеством строк интересующей таблицы на момент вызова этой функции. К моменту Вашего вызова getItem количество строк может измениться, причем как в большую, так и в меньшую строну (в меньшую, правда, может только для таблиц FuturesLimit, MoneyLimit, DepoLimit; также все таблицы обнуляются при рестарте Quikа, о чем мы узнаем по OnCleanUp).
Поэтому мой скрипт считывает (по getItem) содержимое интересующих таблиц лишь один раз (на старте в потоке main), а далее поддерживает их актуальное состояние, ориентируясь по колбекам. Причем, как я уже писал в предыдущем своем посте, колбеки не манипулируют с внутренними (дублирующим Quik) таблицами скрипта, а лишь вешают полученные данные в очередь на обработку для потока main (очередь, правда, поддерживает систему приоритетов, но это уже детали реализации). И делается это именно для того, чтобы не возникала ситуация, при которой пока main будет в целом анализировать, например, таблицу FuturesLimit (точнее ее дубликат), ее количество строк, или уже обработанная строка, вдруг поменяются. Лишь закончив такой анализ, main займется разбором накопившихся за это время необработанных данных из очереди, которые ему навешали колбеки, для поддержания своих внутренних таблиц в актуальном состоянии.
То же касается и обработки OnCleanUp: колбек не очищает внутренние (дублирующие) таблицы скрипта, поскольку это закончится катастрофой, если main в этот момент занят анализом такой таблицы в целом. Он лишь вешает на обработку для main задачу их очистить. Разумеется такая задача имеет наивысший приоритет, чтобы main не занимался обработкой данных от колбеков, поступивших до OnCleanUp, поскольку их информация уже не актуальна.
 
Алексей, Спасибо. Все очень подробно. Но у Вас копия всех актуальных данных в памяти будет храниться, разве это хорошо?
У меня роботы торгуют по таблице всех сделок. Мне ее копию хранить не хотелось бы)
можно конешно взять еще бд под это дело подгрузить но зачем, ведь все данные уже есть в квике.
А говорите функционал с синхронизацией не нужен..

А про ф-цию seachitems подскажите?
она заранее данные для колбека подготавливает или также как цикл в примере через getitem их забирает и в колбек передает?
Хотя судя по тому что там обработка колбека тоже в мейне всеже для синхронизации это не подойдет.
я незнаю с чего вы против того чтоб добавляли такой функционал хотя он даже вам нужен. х2 по памяти. брр.. пусть делают, развивают. не нравиться такая концепция пусть другую дадут. проблема очевидная и все ее в меру возможностей решают внутри скриптов.
 
Антон, В отношении таблицы всех сделок (Таблицы обезличенных сделок) Вы совершенно правы. хранить целиком всю ее копию быссмысленно.
Я писал про "Конкретно приведенная Вами задача", когда "в мейне запрашивается список сделок или заявок". И показал как я решил эту задачу без "критических секций".
Вы хотите подвесить поток Quik пока main занят своим критическим кодом, не допускающим внесение изменений другими потоками в рабочие таблицы скрипта? По мне, так подвешивание потока Quik совершенно недопустимо ни при каких обстоятельствах.
Однако, если скрипту по поступившему через колбек событию необходимо предельно быстро (менее, чем за 0,01 с) принять решение и выдать транзакцию, то весь код принятия решения придется запихнуть в колбек, и тогда пусть весь остальной мир подождет :).
Мое решение позволяет потоку main быть уверенным, что он, в критические моменты, анализирует статический слепок таблицы Quikа, не опасаясь того, что в реальную таблицу Quikа, во это время параллельные потоки могут вносить изменения. Причем этот слепок хранится в виде индексированной по необходимым полям lua-таблицы, поэтому, чтобы найти в ней нужную строку, не нужно перебирать всю таблицу.

Что касается Таблицы обезличенных сделок, то в этой таблице никакая из уже имеющихся строк поменяться не может, в нее могут только добавляться новые строки. Что здесь нужно синхронизировать?
В момент старта "критического" анализа выяснили сколько строк в таблице и занимайтесь ими, игнорируя новые. Хотя я не представляю себе торговой стратегии, при которой необходимо каждый раз заново анализировать Таблицу обезличенных сделок за некоторый исторический период (кроме как один раз на старте скрипта). Поступила новая строка - получили 3 колбека: собственно OnAllTrade (все инструменты в куче), OnParam (все интересующие Вас инструменты и параметры в куче) и пользовательский от CreateDataSource (Quik уже отфильтровал для Вас только интересующий инструмент; применимо, если Вас интересует только цена последней сделки). Обработали поступившую новую цену (или значение другого заказанного параметра) инструмента в колбеке: получили новое состояние "индикатора", например, покупать-продавать-держать (практически любой индикатор теханализа можно вычислить исходя только из его текущего состояния и новой цены). Поток main, когда возникла необходимость, обращается к единственной переменной "состояние индикатора" и принимает решение. Необходимости блокировать какую-либо область памяти на период "критической" обработки не возникает.

Опять написал много буков...:)
Возможно я не верно понял. что Вы подразумеваете под "функционал с синхронизацией"? Я исходил из того, что подразумевается блокирование доступа для других потоков к общим ресурсам (таблицам), когда один из потоков с ними работает.

P.S. SearchItems работает так же, как если бы Вы сами организовали цикл вызовов getItem для строк со start_index по end_index, просто гораздо быстрее. В пользовательский fn попадает копия исследуемой строки (точнее даже не вся строка, а только запрошенные в params поля).
 
P.P.S. И разумеется скрипт хранит не дубликаты таблиц Quikа, а только те поля, которые могут понадобится при последующих обращениях и собственные поля - результаты текущей обработки.
 
Алексей, Там скрипт - tcp сервер. Роботы к нему подключаются, получают обновления и текущие данные, транзакции отправляют. конектор вобщем.
Отсуда и требование получать списки с данными в произвольный момент времени. ( в момент подключения, а потом только обновления)

Доделал локинг через ssort. Обновления отправляю в очередь.

Вобщем все же уже есть (только в документации как всегда пусто).
Передаем в ssort таблицу на 2 элемента и заглушку которая вызывает пользовательский колбек. (интересно как сделать чтобы не передавать таблицу каждый раз?)
Почему бы не добавить функцию в qlua которая будет делаеть тоже самое но без заглушки?
res = scall( function, params...); 2 варианта (с ожиданием и без)
 
Цитата
Антон написал:

Вобщем все же уже есть (только в документации как всегда пусто).
Передаем в ssort таблицу на 2 элемента и заглушку которая вызывает пользовательский колбек. (интересно как сделать чтобы не передавать таблицу каждый раз?)
Почему бы не добавить функцию в qlua которая будет делаеть тоже самое но без заглушки?
res = scall( function, params...); 2 варианта (с ожиданием и без)
Полгода назад я предлагал сделать такой вызов, но результат, к сожалению, - "ваше предложение зарегистрировано".
Тем не менее, сейчас речь о том, что ряд функций QLua непотоконебезопасны даже в случае синхронизации относительно объекта, передаваемого в EnterCriticalSection внутри ssort. Мой скрипт выше воспроизводит такую ошибку.
Страницы: 1
Читают тему
Наверх