как общаются задачи

как общаются задачи

24 апреля 2026, 10:16
Maxim Kuznetsov
0
8

В честь состоявшегося релиза ATcl - краткий пример, как с его помощью делается взаимодействие разных программ

Будем использовать "Thread Shared Variables" - переменные разделяемые между нитями одного приложения.

В Tcl это реализуется в пакете Thread, командами tsv::*  https://www.tcl-lang.org/man/tcl9.0/ThreadCmd/tsv.html 

В отличии от TerminalGlobalVariables там можно сказать двух-уровневая адресация "имя_переменной" + "имя элемента" и сохранять можно не только double, а любые данные. И всё только в памяти, автоматически на диск не сохраняется (хотя и это можно выборочно настроить)

Дальше проще всего пояснять на примере.

Набросаем простой советник, который:

1) считает и публикует пришедшие тики

2) принимает сообщения и что-то с ними делает. Просто печатает.

#include <ATcl/ATcl.mqh>

input string KEY="tsvdemo";

ATcl *tcl=NULL;

// литералы, то есть имена-строки которыми часто пользуемся
Tcl_Obj str_key;
Tcl_Obj str_tickCounter;
Tcl_Obj str_tickInfo;
Tcl_Obj str_incoming;

Tcl_Obj proc_tsvset;
Tcl_Obj proc_tsvincr;
Tcl_Obj proc_tsvpop;

int OnInit()
{
   // инит библиотеки
   ATcl_OnInit();
   // создаём интерпретатор (в принципе их можно сделать много)
   tcl = new ATcl();
   tcl.OnInit();

   // создаём литералы
   str_key=tcl.Ref(tcl.Obj(KEY));   
   str_tickCounter=tcl.Ref(tcl.Obj("tickCounter"));
   str_tickInfo=tcl.Ref(tcl.Obj("tickInfo"));
   str_incoming=tcl.Ref(tcl.Obj("incoming"));
   
   proc_tsvset=tcl.Ref(tcl.Obj("tsv::set"));
   proc_tsvincr=tcl.Ref(tcl.Obj("tsv::incr"));
   proc_tsvpop=tcl.Ref(tcl.Obj("tsv::pop"));
   
   // нам нужен пакет Thread
   tcl.Eval("package require Thread");
   // зададим в интерпретаторе значение из input
   tcl.Set("KEY",KEY);
   // инициализируем данные
   // "атомарно для KEY: если нет поля tickCounter то создать его с 0"
   tcl.Eval("tsv::lock $KEY { if { ! [ tsv::exists $KEY tickCounter ] } { tsv::set $KEY tickCounter 0 } }");
   // запустим таймер - по нему будем считывать входящие сообщения
   EventSetTimer(60);
   
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   if (tcl!=NULL) {
      // удаляем литералы
      tcl.Unref(proc_tsvpop);
      tcl.Unref(proc_tsvincr);
      tcl.Unref(proc_tsvset);
      tcl.Unref(str_key);
      tcl.Unref(str_tickCounter);
      tcl.Unref(str_incoming);
      // деинит интерпретатора
      tcl.OnDeinit(reason);

      delete tcl;
      tcl=NULL;
   }
   // деинит библиотеки
   ATcl_OnDeinit(reason);
}

void OnTick()
{
   // для демонстрации: будем считать вызовы OnTick и писать последний тик
   tcl.Call(proc_tsvincr,str_key,str_tickCounter); // увеличиваем счётчик
   MqlTick tick;
   if (SymbolInfoTick(_Symbol,tick)) {
      // записываем инфу про тик
      // tsv::set $KEY tickInfo [ list $_Symbol $timeMsc $bid $ask ]
      tcl.Call(proc_tsvset,str_key,str_tickInfo,tcl.List(tcl.Obj(_Symbol),tcl.Obj(tick.time_msc),tcl.Obj(tick.bid),tcl.Obj(tick.ask)));
   }
}

void OnTimer()
{
   // для демонстрации - считываем всё из очереди и реагируем
   // tsv::pop $KEY incoming
   if (tcl.Call(proc_tsvpop,str_key,str_incoming)!=TCL_OK) {
      return;
   }
   Tcl_Obj msgs = tcl.Ref(tcl.Result());
   int total=tcl.Count(msgs); // всего получено сообщений из очереди
   // цикл по всем полученным сообщениям
   for(int i=0;i<total;i++) {
      int code = (int)tcl.Long(msgs,i,0);   // код сообщения. i-е сообщение из msgs, элемент 0
      switch(code) {
         case 1:
            // код 1 - просто печатаем
            Print(tcl.String(msgs,i,1));   // текст сообщения. i-е сообщение из msgs, элемент 1
         break;
         case 2:
            // код 2 - выдаём алерт
            Alert(tcl.String(msgs,i,1));
         break;
      }
   }   
   tcl.Unref(msgs);
}

Сделали, запускаем на свободном чарте.

Откроем ещё один чарт и запустим там советник-консоль из состава ATcl. Для теста - будет играть роль второго компонента

И будем вводить в консоль команды:


подключаем пакет Thread: package require Thread

смотрим есть-ли переменные с нужным именем (по умолчанию было tsvdemo): tsv::names tsvd*

смотрим какие есть элементы : tsv::array names tsvdemo

читаем счётчик и инфу о тике: tsv::get tsvdemo tickCounter , tsv::get tsvdemo tickInfo

и наконец-то посылаем в советник (в очередь incoming) несколько сообщений вида { код текст }:

  tsv::lpush tsvdemo incoming [ list 1 "Hello from console" ]

или так:

  tsv::lpush tsvdemo incoming { 2 "test alert" }


собственно всё..Данные из любой другой программы то есть советника/индикатора/скрипта мы прочитать можем и организовать их общение тоже.

Надо только продумать систему именований и форматы. 

Всех конечно интересует насколько быстро всё работает.

Ну так вот быстро:

на картинке - справа отображён полный биржевой стакан, который в базе в сжатом виде занимает 40-50k (а тут передаётся не сжатым), 10 раз/сек

а снизу объёмы сделок и скользящая сумма, сделок прилетает до 10 тыс/минуту

ничего не тормозит и подчас опережает "путь через терминал"

----

Релиз ATcl 1.11 https://www.mql5.com/ru/blogs/post/769153