Модуль очереди для обмена между потоками

Опубликовано в QLua

Модуль FIFO очереди для обмена данными между потоком терминала (колбеками) и потоком скрипта QLua.

 

-- Модуль работы с очередями колбеков
local queue =
{
-- Создать новую очередь FIFO
new = function(self)
            self.__index = self
return setmetatable( { first = 0,
last = -1
},
self)
end, --------------------------------------------------------------------------------------------
-- Поместить значение в очередь
push = function(self,value)
local last = self.last + 1
self[last] = value
self.last = last
end, ---------------------------------------------------------------------------------------------
-- Изъять самое старое значение из очереди. Если очередь пуста, возвращается nil
pop = function(self)
local first = self.first
if first <= self.last then
local value = self[first]
self[first] = nil
self.first = first + 1
return value
end
end, ---------------------------------------------------------------------------------------------
-- Количество значений в очереди
size = function(self)
return self.last - self.first + 1
end ---------------------------------------------------------------------------------------------
}

Комментарии   

# Егор 20.01.2015 15:51
Зачем это нужно?
# admin 20.01.2015 16:35
Цитирую Егор:
Зачем это нужно?


Егор. Модель построения скриптов на qlua - двухпоточная. Грубо можно сказать так: есть 2 процессора, которые независимо друг от друга крутят два потока исполнения. Один поток - это колбеки. Второй - это функция скрипта main() и все что из нее вызывается.

Теперь представим ситуацию, что колбек onAllTrade складывает в таблицу trades все приходящие сделки. То есть использует функцию, например, table.insert, добавляя информацию в конец таблицы.

Второй поток (который main) берет из этой таблицы первую сделку и удаляет её, используя table.delete

Оба процесса работают совершенно независимо друг от друга - так устроен qlua.

При этом обязательно (когда-нибудь, не сразу) возникнет конфликт.

Пример:

Функция table.insert узнала длину таблицы trades. Допустим, она равна 5
В этот момент table.delete из потока main удаляет первую строку. Таким образом, длина таблицы становится равна 4.

Теперь table.insert продолжает свою работу и увеличивет длину таблицы на 1, записывая новую сделку на 6(!!!!) место.

Таким образом, рвется структура таблицы - элемент с номером 5 пуст.

Факт, что архитектура qlua использует потоки и при этом не является потокобезопасно й, относительно известный. Пока приходится строить очереди типа той, что приведена в статье.

В ближайшем будущем в qlua появятся функции типа table.sinsert, table.sremove, table.sforeach и некоторые другие, которые будут уже потокобезопасны ми и надобность в данном модуле отпадет. Но пока нужно использовать его.
# Alpinist573 21.09.2015 22:24
... и удаляет её, используя table.delete

надо видимо читать как:

... и удаляет её, используя table.remove
# admin 22.09.2015 08:17
Да, конечно.
# Егор 20.01.2015 16:58
Доступно объяснили. Спасибо. Видимо у меня из-за этого вылетает QUIK "c дампом". Ни как не могу отловить ошибку.
В основном вылеты при выставлении-сня тии ордеров и при более 40 "закрытых" ордерах в таблице Заявок.
Правда я делаю на с++.
# Егор 20.01.2015 18:05
Еще бы пример, как этой очередью пользоваться?
# admin 20.01.2015 18:28
Схематично:

function OnAllTrade(trad e)
q:push(trade)
end

function main()
q = queue:new()
while q:size() > 0 do
process(q:pop() )
end
end
# Kalinin4 17.04.2015 22:54
Переписал по совету Михаила свой скрипт на полностью событийную модель. За основу решил взять этот код. Скрипт перестал взрываться по непонятным причинам.
# AndreyPool 13.12.2016 11:04
Добрый день, подскажите для корректной работы main и OnTrade, OnStopOrder, OnOrder использование очереди ещё актуально? Или уже реализован другой механизм?
Quik 7.5.0.72
Заранее спасибо.
# admin 13.12.2016 13:14
Здравствуйте

С некоторых пор в qlua бвли добавлены функции table.s*()

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

Для простых роботов их хватит. Однако у функций table.s*() есть один серьезный недостаток и я отказался от их использования в своих скриптах. Я использую нечто похожее на тот способ, который приведен в этой статье
# AndreyPool 13.12.2016 13:37
Спасибо за ответ. Очередь можно прописать внутри скрипта или создать отдельно как будет эффективнее?
# admin 13.12.2016 14:22
Ээээ. Вот бы понять ваш вопрос. Очередь - это набор функций. Одна помещает значение в очередь, другая достаёт, остальные предоставляют какой-то сервис. Понятно, что эти функции есть часть вашего скрипта.

Или я не понимаю вопрос?
# AndreyPool 13.12.2016 14:25
Создать отдельный dll файл и подключать его к основному скрипту или внутри основного скрипта функции прописать. Что будет эффективнее?
# admin 13.12.2016 14:34
В 99% случаев разницы не будет. Поэтому имеет смысл использовать текст на луа.

Если вы счастливчик и ваша задача попадает в оставшиеся 1% - тогда и будете потеть с написанием очередей на С++...
# AndreyPool 13.12.2016 14:38
Спасибо за совет)
# Phil 02.02.2017 17:23
С данной очередью работает лучше, но все равно QUIK падает с Access Violation в qlua.dll.
Просто раньше мой код падал где-то за 5-10 мин, а с данной очередью работает дольше - может и час проработать.

Проблема думаю в том, push и pop должны вызываться из разных потоков строго по одному, а сейчас, видимо, происходит прерывание потока в push или pop и передача контроля другому. Проблему легко решить через crititcal section или mutex, но никак не могу найти как это реализовать в Lua.
# Neznakomec 06.08.2019 10:46
Если я хочу вынести объект в локальный файл queue.lua,
то получается нужно убрать local queue
а в других файлах написать require("queue" )
# admin 06.08.2019 11:20
Самый простой вариант - ничего не менять, а включить файл в ваш скрипт через loadfile()

Local можно оставить, можно убрать и сделать глобальным - это на вкус...
# Neznakomec 06.08.2019 10:47
А какие максимальные значения у целых чисел в квике?
Если очередь слишком активная, мы не перейдем эти границы в first или last, хм?
# admin 06.08.2019 11:20
Даже не сомневайтесь))

Это не целые числа, а doublefloat
# Neznakomec 06.08.2019 12:00
Я думал это int 32 бита.
Если здесь вещественный doublefloat используется для
local last = self.last + 1
то это вообще странно.

UPD:
www.lua.org/pil/2.3.html
всё я понял к чему вы клоните.
Да, таких больших чисел хватит с запасом))

Недостаточно прав для комментирования

Архив QLua