1

Тема: LUA Coroutines Tutorial

Coroutines Tutorial

Что есть ко-рутины?

  Ко-рутины или сопрограммы, позволяют нам выполнять несколько задач одновременно. В любом месте и в любое время, мы можем прервать любую функцию (в данном случае, ко-рутину), передать управление другой, а потом вернуться, как ни в чём не бывало в прерванную ко-рутину и продолжать с того места откуда она была прервана. Таким образом, достигается псевдо-многозадачность в рамках LUA-кода одной виртуальной машины.

Multi-Threading

  Каждая задача выполняется в потоке, который является отдельным от других потоков. Под многопоточностью (Multi-Threading) часто подразумевают одновременное выполнение нескольких задач.

  Есть разные способы, с помощью которых многопоточность может быть реализована. Некоторые системы выделяют определенное количество времени для каждого потока, по прошествии которого передают управление на следующий поток и т.д. Это называется pre-emptive multi-threading (или упреждающая многопоточность). В этом случае каждому из потоков не нужно беспокоиться о том, как много времени он занимает, он больше озабочен своей функцией.

  В других системах, наоборот, поток знает сколько он должен выполняться. Это называется cooperative, или collaborative multi-threading. Здесь все потоки сотрудничают вместе, чтобы разрешить приложению работать должным образом. Это тип многозадачности, который используют сопрограммы Lua.

  Не стоит путать потоки операционной системы и потоки и сопрограммы (ко-рутины) LUA. Сопрограммы - блоки Lua-кода, которые создаются в Lua, и имеют свой собственный поток управления, также как и потоки ОС. Только одна сопрограмма работает в одно и то же время, пока не будет активирована другая подпрограмма, или пока не будет возврата к той подпрограмме, которая её вызвала. Сопрограммы - способ дать возможность псевдо-одновременного исполнения многим другим сопрограммам удобным и естественным образом, однако, это нельзя назвать чистым параллелизмом, и, таким образом, нет никакого выигрыша в производительности при использовании нескольких процессоров. Однако, поскольку сопрограммы переключаются намного быстрее, чем потоки в операционной системе  и обычно не требуют сложных, а иногда и дорогих механизмов блокировки, применение сопрограмм, как правило, быстрее, чем эквивалентные программы, использущие полноценные потоки ОС.

Yielding

Чтобы осуществить вышесказанное несколько сопрограмм должны прекратить выполнение (после выполнения разумного объема обработки) и передать управление другому потоку. Это называется "yielding". Сопрограммы явно должны вызвать функцию Lua coroutine.yield() , которая похожа на использование return-функции. Однако, в отличии от return - мы можем снова вернуться в прерванную функцию и продолжить с того места где мы остановились. При выходе же по return мы безвозвратно теряем все локальные переменные в той функции из которой вышли.

Довольно слов, несколько примеров:

Чтобы создать сопрограмму, мы должны иметь функцию, которая её представляет, например,

> function foo()
>>   print("foo", 1)
>>   coroutine.yield()
>>   print("foo", 2)
>> end
>

Мы создаем подпрограмму с помощью coroutine.create(fn) функции. Мы передаем ей точка входу для потока, который является функцией Lua. Объект, возвращаемый Lua является поток:

> co = coroutine.create(foo) -- create a coroutine with foo as the entry
> = type(co)                 -- display the type of object "co"
thread

Мы можем узнать, в каком состоянии находится поток, используя для этого функцию coroutine.status(), например,

> = coroutine.status(co)
suspended

  Состояние  "suspended" означает, что поток жив, и, как и следовало ожидать, ничего не делает. Обратите внимание, что, когда мы создали поток - он НЕ начинает выполнение. Чтобы начать выполнение, мы используем функцию  coroutine.resume(). После её вызова Lua будет входить в поток и выходить из него по coroutine.yield().

> = coroutine.resume(co)
foo     1
true

  функция coroutine.resume() возвращает статус ошибки после своего вызова. Выше показано, что мы вошли в функцию foo , а затем вышел без ошибок. Интересный момент: с функцией такой бы номер не прошёл, но с сопрограммами мы сможем возобновить выполнение снова:

> = coroutine.resume(co)
foo     2
true

  Видно, что мы выполнили строку после выхода в foo и снова вернулся без ошибок. Тем не менее, если мы посмотрим на status мы можем видеть, что мы покинули функцию foo и сопрограмма прекращается.

> = coroutine.status(co)
dead

Если мы попытаемся провернуть этот номер снова - мы получим сообщение об ошибке:

> = coroutine.resume(co)
false   cannot resume dead coroutine

  После того, как сопрограмма закончила свою работу - она не может быть возобновлена.

Подробнее

  Ниже более сложный пример демонстрирует некоторые важные особенности сопрограмм.

> function odd(x)
>>   print('A: odd', x)
>>   coroutine.yield(x)
>>   print('B: odd', x)
>> end
>
> function even(x)
>>   print('C: even', x)
>>   if x==2 then return x end
>>   print('D: even ', x)
>> end
>
> co = coroutine.create(
>>   function (x)
>>     for i=1,x do
>>       if i==3 then coroutine.yield(-1) end
>>       if i % 2 == 0 then even(i) else odd(i) end
>>     end
>>   end)
>
> count = 1
> while coroutine.status(co) ~= 'dead' do
>>   print('----', count) ; count = count+1
>>   errorfree, value = coroutine.resume(co, 5)
>>   print('E: errorfree, value, status', errorfree, value, coroutine.status(co))
>> end
----    1
A: odd  1
E: errorfree, value, status     true    1       suspended
----    2
B: odd  1
C: even 2
E: errorfree, value, status     true    -1      suspended
----    3
A: odd  3
E: errorfree, value, status     true    3       suspended
----    4
B: odd  3
C: even 4
D: even         4
A: odd  5
E: errorfree, value, status     true    5       suspended
----    5
B: odd  5
E: errorfree, value, status     true    nil     dead
>

В основном у нас есть цикл for, который вызывает две функции: odd() , когда он сталкивается с нечетным числом, и even() на четных чисел.

Оригинал на англ.: http://lua-users.org/wiki/CoroutinesTutorial
Перевод: sam063rus
за что ему большое спасибо! admin

2 (2016-09-29 11:56:47 отредактировано sam063rus)

Re: LUA Coroutines Tutorial

-

3 (2016-09-29 11:55:28 отредактировано sam063rus)

Re: LUA Coroutines Tutorial

-

4 (2016-09-29 11:55:19 отредактировано sam063rus)

Re: LUA Coroutines Tutorial

-

5

Re: LUA Coroutines Tutorial

А есть примеры, где бы это использовалось в реальных скриптах под QUIK?

6

Re: LUA Coroutines Tutorial

CyberTrader пишет:

А есть примеры, где бы это использовалось в реальных скриптах под QUIK?

нет ни примеров, ни конкретного смысла использования корутин в торговле

7 (2016-09-29 11:46:19 отредактировано sam063rus)

Re: LUA Coroutines Tutorial

-

8 (2016-09-29 11:45:41 отредактировано sam063rus)

Re: LUA Coroutines Tutorial

-

9

Re: LUA Coroutines Tutorial

kalikazandr пишет:
CyberTrader пишет:

А есть примеры, где бы это использовалось в реальных скриптах под QUIK?

нет ни примеров, ни конкретного смысла использования корутин в торговле

Вот пример использования в торговле: http://smart-lab.ru/blog/239078.php . Есть ли у Вас замечания к такому использованию coroutines ?

10

Re: LUA Coroutines Tutorial

Dr. Robotnik пишет:

Вот пример использования в торговле: http://smart-lab.ru/blog/239078.php . Есть ли у Вас замечания к такому использованию coroutines ?

Замечаний нет, если человек не знает как получить данные из таблиц (не nil) или не может связать между собой пару тройку потоков без "костылей", то конечно, такой способ пойдет.
Пока его первая корутина получит ответ,  у меня робот успеет еще заявок 20 выставить и спокойно обработать все.
Цитата:
"...Таким образом вызов placeStopOrder() остановит выполнение основной логики до тех пор, пока стоп-приказ не будет выставлен на 100%. Основной код при этом не страдает из-за лишних проверок, отслеживания событий и т.п..."

Асинхронность для человека написавшего такое - пустой звук. Бред вообще, стиснув зубы не буду матерится.

Не убедительно.

11

Re: LUA Coroutines Tutorial

kalikazandr пишет:
Dr. Robotnik пишет:

Вот пример использования в торговле: http://smart-lab.ru/blog/239078.php . Есть ли у Вас замечания к такому использованию coroutines ?

Замечаний нет, если человек не знает как получить данные из таблиц (не nil) или не может связать между собой пару тройку потоков без "костылей", то конечно, такой способ пойдет.
Пока его первая корутина получит ответ,  у меня робот успеет еще заявок 20 выставить и спокойно обработать все.
Цитата:
"...Таким образом вызов placeStopOrder() остановит выполнение основной логики до тех пор, пока стоп-приказ не будет выставлен на 100%. Основной код при этом не страдает из-за лишних проверок, отслеживания событий и т.п..."

Асинхронность для человека написавшего такое - пустой звук. Бред вообще, стиснув зубы не буду матерится.

Не убедительно.

Спасибо за комментарий. Как я понял по ссылке упрощённый пример, т.е. вся логика останавливается просто потому, что была поставлена такая задача. Сам пользуюсь костылём для сведения нескольких событий, очерёдность которых может меняться в виде создания элемента массива (в случае отсутствия такового) с ключом соответствующим номеру или транс ид перед попыткой обращения к нему, и обновлением его значений в случае получения обновлённых данных. Просто подумалось, что стоит заменить это вышеописанным подходом, но раз от этого никаких преимуществ, а по быстродействию только проигрыш, оставлю как есть.

12

Re: LUA Coroutines Tutorial

Dr. Robotnik пишет:

Спасибо за комментарий. Как я понял по ссылке упрощённый пример, т.е. вся логика останавливается просто потому, что была поставлена такая задача. Сам пользуюсь костылём для сведения нескольких событий, очерёдность которых может меняться в виде создания элемента массива (в случае отсутствия такового) с ключом соответствующим номеру или транс ид перед попыткой обращения к нему, и обновлением его значений в случае получения обновлённых данных. Просто подумалось, что стоит заменить это вышеописанным подходом, но раз от этого никаких преимуществ, а по быстродействию только проигрыш, оставлю как есть.

Конечно, так быстрее.
Дополнительно можно сделать проверку в колбеке не активных заявок и по ним не принимать данные, в случае смены сервера и т.п., когда заново приходят все колбеки.
Кстати транс ид может быть 0 при первом колбеке, даже если вы ему присваивали не 0, дублируйте в brokerref и считывайте оттуда:

 local id = 0--ручные заявки
  local ref = order.brokerref
  if string.len(ref) > 0 then
     local _, b = string.find(ref, '/+')
     id = tonumber(string.sub(ref, b + 1)) or 0
  end

sep = "/"--для фортс
sep = "//"--ммвб
в транспарам:
TRANS_ID = trans_id
CLIENTCODE = clientcode..sep..trans_id

13

Re: LUA Coroutines Tutorial

kalikazandr, спасибо за совет! С нулевым транс ид при первом коллбеке не сталкивался, но теперь буду иметь это ввиду.

14

Re: LUA Coroutines Tutorial

Dr. Robotnik пишет:

kalikazandr, спасибо за совет! С нулевым транс ид при первом коллбеке не сталкивался, но теперь буду иметь это ввиду.

Не за что, надо помнить, что поле CLIENTCODE составное и 20-ти символьное, если в сумме clientcode + sep + trans_id больше 20 символов - заявка не пройдет проверку на стороне терминала.