Добрый день, Всем С момента появления VMLua в КВИКЕ неоднократно рассказывал о том, что применение event вместо sleep не только экономит ресурсы процессора, но и позволяет не пропускать события в колбеках и максимально бысто на них реагировать. говорил об этом например здесь: https://forum.quik.ru/forum17/topic8426/ --------------------- К сожалению, кроме бессмысленного хамства некоторых посетителей форума, ничего конкретного никто не написал. --------------- Но вот наконец-то появился вменяемый чел paluke . и после моей попытки в очередной раз объяснить преимущество event https://forum.quik.ru/messages/forum10/message75435/topic8600/#message75435
он все же решил проверить это и убедился, что это так действительно:
Код
Просто проверка концепции:
Кодw32 = require("w32")
run = true
evt = false
function OnInit()
evt = w32.CreateEvent(nil, 0, 0, nil)
end
function OnStop()
run = false
w32.SetEvent(evt)
end
function main()
while run do
w32.WaitForSingleObject(evt, 1000000)
end
w32.CloseHandle(evt)
end
В колбеках вызываете SetEvent - main сразу просыпается.
Присоединяюсь. Не нужно иметь много образования чтобы понять что техника с ожиданием событий эффективна. А реализация не так сложна, как кажется на первый взгляд.
Для моих целей сообщения тоже оказались заметно быстрее, чем sleep. На скрине для каждой пары неродных для квика стаканов левый отрисован с ожиданием через сообщения, правый - через sleep. Внизу стаканов статистика потерь, они на правых стаканах больше. Kernel time и cpu time тоже меньше на потоке, который ждёт событий. Поскольку рисование стаканов идёт в main скрипта, нельзя использовать WaitForMultipleObjects для событий из OnQuote и OnStop, нужно MsgWaitForMultipleObjectsEx, чтобы main включался на событиях GUI для стаканов. Её нет в w32, пришлось подключать через cffi-lua. Инструкция по сборке написана тут. Если сравнивать время исполнения между w32 и cffi-lua, то конечно же w32 быстрее (SetEvent на 12%, CreateEvent на 23%). Важные части кода:
Код
local w32 = require "w32-ext"
local ffi = require "cffi"
local SetEvent = w32.SetEvent
local MsgWaitForMultipleObjectsEx = w32.MsgWaitForMultipleObjectsEx
local cb_t = {'EDM4','SiM4','CRM4','CNYRUB_TOM'}
local e_quote, e_stop
local is_run = true
function OnQuote(class_code, sec_code)
local sent = cb_t[sec_code]
if sent then
cb_t[sec_code] = sent + 1
SetEvent(e_quote)
end
end
function OnStop()
is_run = false
SetEvent(e_stop)
end
function main()
e_stop = w32.CreateEvent(nil, 0, 0, nil)
e_quote = w32.CreateEvent(nil, 0, 0, nil)
local events = ffi.new("HANDLE[2]", {e_quote, e_stop})
local WAIT_TIMEOUT = w32.WAIT_TIMEOUT
while is_run do
local res = MsgWaitForMultipleObjectsEx(2, events, 500, 7423, 0) -- QS_ALLINPUT == 7423
if res == 1 then break end
if res == WAIT_TIMEOUT then
-- по таймауту обработать сообщения, например, о смене foreground окна
else
-- обновить стаканы
end
end
end
end
w32-ext.lua:
Код
local w32 = require("w32")
local ffi = require "cffi"
ffi.cdef [[
typedef unsigned int UINT;
typedef unsigned long DWORD;
typedef long long LRESULT;
typedef long long LPARAM;
typedef unsigned long long WPARAM;
//typedef void* HWND; // in windows
typedef intptr_t HWND; // uintptr_t not ok as it appends 'ULL', LPARAM also ok
typedef intptr_t HANDLE;
void SetLastError(DWORD);
//LRESULT SendMessageA(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
HWND GetForegroundWindow();
DWORD MsgWaitForMultipleObjectsEx(DWORD nCount, const HANDLE *pHandles, DWORD dwMilliseconds, DWORD dwWakeMask, DWORD dwFlags);
]]
w32.SetLastError = ffi.C.SetLastError
w32.GetForegroundWindow = ffi.C.GetForegroundWindow
w32.MsgWaitForMultipleObjectsEx = ffi.C.MsgWaitForMultipleObjectsEx
-- those calls trigger 127 error on first invocation
w32.SetLastError(0)
return w32
и оба коллбэка OnAllTrade и OnQuote почти одновременно выдали
Код
SetEvent(evt)
При вторичной выдаче SetEvent(evt) эта установка события не выполнится? Теперь вызывается main и начинает их обрабатывать событие: она проверяет и обрабатывает общую очередь от обоих коллбэков. Это правильная работа с событиями?
Как надо по-минимуму изменить параметры CreateEventA, WaitForSingleObject и SetEvent, чтобы main знала, какой именно коллбэк выдал SetEvent(evt)?
Насколько я понял, надо использовать параметр lpName в CreateEventA: коллбэки создают события со своими именами. Осталось уточнить: что будет, если оба коллбэка успеют создать события, а main ещё не запустилась для их обработки. Не будет ли потеряно второе событие?
Serge123 написал: Насколько я понял, надо использовать параметр lpName в CreateEventA: коллбэки создают события со своими именами. Осталось уточнить: что будет, если оба коллбэка успеют создать события, а main ещё не запустилась для их обработки. Не будет ли потеряно второе событие?
createEvent создает не событие, а объект "событие) упрощенно это значит, что OS выделяет для флага события ячейку и вернет его имя Имя флага всегда уникально. ----------------- Далее мы либо устанавливаем Set флаг или сбрасываем Reset. Wait.. - это как бы аналог условного цикла проверки состояния флага c заданием времени ожидания. Проверку делает OC а поток бездействует пока Wait ------------ Если событий много то используем wait дя нескольких событий либо более сложные функции ожидания (семафоры и т д) ================= Если есть желание изучить механизмы синхронизации потоков рекомендую книгу:
Насколько я понял, т.к. в моём случае параметр dwFlags у CreateEventA равен 0, то ResetEvent не требуется. Похоже, когда Виндовс по установленному коллбэком флагу события запускает main, то Виндовс сбрасывает этот флаг, поэтому в принципе возможна потеря возникновения события. Ясно что события не буферизуются. Ожидание множества событий тут по-моему не очень подходит, т.к. main должна отрабатывать при возникновении хотя бы одного события.
Такую книжку Рихтера (только в синей ламинированной обложке) я купил давно, тогда я ещё не программировал на Си. Пока что меня не интересует общая теория семафоров, светофоров и шлагбаумов: я сейчас конкретно переписываю скрипт с Луа на Си dll, отвлекаться на эксперименты неохота. Пока у меня обработка событий на Си происходит в коллбэках и я собираюсь перенести её в main с использованием очереди и объектов событий, как это у меня работает в Луа.
Тут возникла новая проблема: если обработчик OnAllTrade на Си уже не будет обрабатывать этот вызов, а будет что-то записывать в очередь и делать выход, то как гарантировать, что память под таблицу alltrade не будет освобождена до обработки этой таблицы в функции main, которая тоже находится в dll? Совсем отключить сборку мусора, или есть что-то ещё?
Serge123 написал: Тут возникла новая проблема: если обработчик OnAllTrade на Си уже не будет обрабатывать этот вызов, а будет что-то записывать в очередь и делать выход, то как гарантировать, что память под таблицу alltrade не будет освобождена до обработки этой таблицы в функции main, которая тоже находится в dll? Совсем отключить сборку мусора, или есть что-то ещё?
могу лишь рассказать как я решил эту проблему. У меня колбеки обрабатываются не только в main, но и в других, новых потоках. Так как все колбеки вызываются последовательно, то нет надобности делать много Event У меня один event (см мои скрипты на форуме) и очередь. Т е вызвал терминал колбек. В колбеке в очередь записывается номер колбека и входные параметры. Если очередь пустая, то устанавливается флаг события. В функции main , если очередь пустая wait ждет событие, иначе проводится обработка очереди. Если колбеки не требуют сложных вычислений, то они обрабатываются внутри main. Для сложной обработки, например , в портфеле множество бумаг, то для принятия решения для каждой бумаги вызывается новый поток из пула потоков ОC. В этом потоке я запускаю LuasJIT (могу запустить python, terra,julia и др) вместо lua, что обеспечивает ускорение вычислений на порядок, по сравнению с вычислениями в main.
nikolz написал: могу лишь рассказать как я решил эту проблему.
К сожалению, в этом ответе не сказано о решении этой проблемы... А именно: как в main (которая сидит в dll) обработать таблицу alltrade, если при завершении работы OnAllTrade (которая тоже сидит в dll) стек её вызова (в котором сидит ссылка на табл. alltrade) должен очиститься? Я вижу такие костыли: - запретить сборку мусора (до 18:45). - dll из OnAllTrade как-то связывается с Lua и Lua дублирует ссылки на alltrade в массив локальных переменных области видимости этого файла lua. Потом dll как-то даёт знать, что каким-то из этих переменных можно присвоить nil. Как это сделать, пока не знаю. М.б. это делается через (light)userdata, но я с этим не связывался.
Как из Си заставить Lua продублировать ссылку на таблицу, если эта ссылка сидит в стеке? Как из Си вызвать скрипт Lua, чтобы он получил этот стек вызова OnAllTrade? Иначе придётся коллбэки делать на Lua и только main на Си, а этого не хочется из-за какой-то потери скорости.
nikolz написал: могу лишь рассказать как я решил эту проблему.
К сожалению, в этом ответе не сказано о решении этой проблемы... А именно: как в main (которая сидит в dll) обработать таблицу alltrade, если при завершении работы OnAllTrade (которая тоже сидит в dll) стек её вызова (в котором сидит ссылка на табл. alltrade) должен очиститься? Я вижу такие костыли: - запретить сборку мусора (до 18:45). - dll из OnAllTrade как-то связывается с Lua и Lua дублирует ссылки на alltrade в массив локальных переменных области видимости этого файла lua. Потом dll как-то даёт знать, что каким-то из этих переменных можно присвоить nil. Как это сделать, пока не знаю. М.б. это делается через (light)userdata, но я с этим не связывался.
Как из Си заставить Lua продублировать ссылку на таблицу, если эта ссылка сидит в стеке? Как из Си вызвать скрипт Lua, чтобы он получил этот стек вызова OnAllTrade? Иначе придётся коллбэки делать на Lua и только main на Си, а этого не хочется из-за какой-то потери скорости.
Попробую объяснить. ------------------ Не важно, что и где сидит. dll - это код программы. Для кода не нужна синхронизация потоков. Синхронизация потоков нужна при обращении к данным. ---------------- Попробую снова объяснить как Ваша проблема решается на моей программе. ОЧЕРЕДЬ и EVENT - эти элементы и решают проблему. ------------------ Поясняю на примере: 1) Терминал вызывает OnAllTrade и передает в стеке указатель на таблицу, которая является новой строкой таблицы alltrade. -------------------- 2) В OnAllTrade указатель на полученную таблицу новой строки записывается в очередь Cохраняется в другой таблице КАРЛ ! ! ----------------------- 3) OnAlltrade заканчивает работу. Если очередь при этом содержит лишь один элемент, то устанавливается флаг EVENT. ---------------------------------- 4) Если функция main была в состоянии WAIT, то она начинает обрабатывать элемент очереди И о чудо, Карл!!! там есть указатель на таблицу новой строки alltrade, при этом onAllTrade давно уже завершилась или может быть даже снова вызвана с новой таблицей новой строки. Ты понимаешь, Карл? Снова хоть тысячу раз!!! -------------------------- Если функция main обрабатывала очередь, то она и продолжает это делать, а новые вызовы onAllTrade подкидывают новые таблицы новых строк alltrade в очередь. -------------------------- И это все. Нет никаких проблем в работе моей программы, Осталась лишь тебе понять то, что я написал Карл!!!
nikolz написал: Нет никаких проблем в работе моей программы,Осталась лишь тебе понять то, что я написал Карл!!!
Я не Карл... Это уже какая-то патология...
Тогда не волнуйтесь. Я вам раньше уже ответил, что мой вариант решает вашу проблему. Но Вы стали утверждать обратное, так как ничего не поняли. -------------------- Я на вашем примере написал ликбез для карла и других читателей. Вы можете это не читать. Я же Вас не заставляю, а Вы меня не нанимали. --------------------- Вместо "спасибо" за то, что я вам разжевал бесплатно что-то и в рот положил, вы надулись как рыба луна. вот это действительно патология. ---------------------------- Я просто погулять вышел, ты понял Карл!!! .