Многопоточность в MQL через CreateThread WinAPI

 

Всем привет

Озадачился вопросом как вынести тяжелый но не критичный процесс в отдельный поток

MQL позволяет загружать функции из DLL

kernel32 содержит разные примочки для многопоточности, решил поэкспериментировать

Пишу следующий код

#define WORD ushort
#define HANDLE int
#define DWORD uint
#define LPDWORD DWORD&
#define SIZE_T int
#define LPVOID int
#define LPSECURITY_ATTRIBUTES int
#define BOOL bool
#define LPCWSTR string
typedef DWORD (*LPTHREAD_START_ROUTINE)(LPVOID);

#define CREATE_SUSPENDED 0x00000004
#define CREATE_NORMAL 0x00

#define WAIT_ABANDONED 0x00000080
#define WAIT_OBJECT_0 0x00000000
#define WAIT_TIMEOUT 0x00000102
#define WAIT_FAILED 0xFFFFFFFF
#define INFINITE 0xFFFFFFFF

#import "kernel32.dll"
HANDLE CreateThread(
   LPSECURITY_ATTRIBUTES lpThreadAttributes,
   SIZE_T dwStackSize,
   LPTHREAD_START_ROUTINE lpStartAddress,
   LPVOID lpParameter,
   DWORD dwCreationFlags,
   LPDWORD lpThreadId // LPDWORD
);

HANDLE CreateRemoteThread(
   HANDLE                 hProcess,
   LPSECURITY_ATTRIBUTES  lpThreadAttributes,
   SIZE_T                 dwStackSize,
   LPTHREAD_START_ROUTINE lpStartAddress,
   LPVOID                 lpParameter,
   DWORD                  dwCreationFlags,
   LPDWORD                lpThreadId
);

DWORD ResumeThread(HANDLE hThread);

HANDLE CreateMutexW(
  LPSECURITY_ATTRIBUTES lpMutexAttributes,
  BOOL bInitialOwner,
  LPCWSTR lpName
);

BOOL ReleaseMutex(HANDLE hMutex);
BOOL CloseHandle(HANDLE hObject);
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);

DWORD WaitForMultipleObjects(
   DWORD nCount,
   HANDLE &lpHandles[],
   BOOL bWaitAll,
   DWORD dwMilliseconds
);

DWORD GetCurrentThreadId();
void ExitThread(DWORD dwExitCode);

HANDLE GetCurrentProcess();
LPCWSTR GetCommandLineW(void);
#import

DWORD MyFunc(LPVOID v)
{
   ulong start = GetMicrosecondCount();
   Sleep(1000);
   Print(__LINE__, " ", (GetMicrosecondCount() - start)/1000);
   return 0;
}

void OnStart()
{
   Print(GetCurrentThreadId(), " ", GetCommandLineW());
   
   DWORD ThreadID = 0;
   
   LPTHREAD_START_ROUTINE func = MyFunc;
   HANDLE ht = CreateThread(0, 0, func, 0, CREATE_NORMAL, ThreadID);
   Print("Handle: ", ht);
   Print("ThreadID: ", ThreadID);
   if (ht != NULL)
   {
      DWORD res_ = WaitForSingleObject(ht, 0);
      Print("-------------------------------- Wait result: ", res_);
      //CloseHandle(ht);
      
      ResumeThread(ht);
      DWORD res = WaitForSingleObject(ht, INFINITE);
      Print("Wait result: ", res_);
      CloseHandle(ht);
   }
}

Комилится и работает до тех пор пока не приходит пора вызова своей функции

Терминал просто вылетает на ResumeThread (там вместо CREATE_NORMAL - CREATE_SUSPENDED, при флаге CREATE_NORMAL поток запускается сразу и тоже вылетает)

В чем может быть проблема? Указатель на функцию в MQL не тоже самое что в обычном С? Что я упускаю?

Пробовал работать с мьютексами, там все норм, типы вроде все правильно переопределил, указатели - просто адреса (числа) на область памяти и для 32 версии используется int, если использовать long то проблемы с DLL

 
MQ мельком упоминал, что Sleep на уровне винды немного паралелит
 
Fast528:
MQ мельком упоминал, что Sleep на уровне винды немного паралелит

Сначала функция вообще пустая была, работало с тем же результатом (т.е. тупо терминал вылетал целиком)

Добавил sleep для теста мьютексов, запускал скрипт на двух графиках и проверял как они синхронизируются

 
Mikhail Zaprudin:

Указатель на функцию в MQL не тоже самое что в обычном С?

Именно так.

 
Mikhail Zaprudin:

Озадачился вопросом как вынести тяжелый но не критичный процесс в отдельный поток

В MQL отдельные потоки можно организовать только запуском дополнительного эксперта или (в ограниченных случаях) скрипта. Передавать команды и данные туда/сюда можно разными способами - на форуме уже обсуждались.

 

Ну тогда уж лучше внешнюю либу куда данные копировать и запускать поток

Возни чуть больше, зато надежнее

 
Mikhail Zaprudin:

Ну тогда уж лучше внешнюю либу куда данные копировать и запускать поток

Возни чуть больше, зато надежнее

Это не надежнее, а единственный вернуть способ.

 

Пробую идти по единственно верному пути, и что-то спотыкаюсь

Делаю DLL с иcпользованием Qt

#include <QtGlobal>
#include <QtCore>
#include <QtMath>
#include <QtConcurrent/QtConcurrent>
#include <windows.h>

#define MQL_EXPORT extern "C" __declspec(dllexport)
#define MT4_WMCMD_UPDATE_DATA 33324

QFuture<void> F; // Пока тестирую с одним индикатором, можно просто глобальную переменную использовать

// Просто ради теста
MQL_EXPORT int test(int a, int b)
{
    return a+b;
}

// Заполнение переданного буфера значениями в отдельном потоке
void fill(HWND hwnd, UINT msg, double v[], int len)
{
    QThread::sleep(3);
    for (int i = 0; i < len; ++i) v[i] = qrand()%500;
}

MQL_EXPORT bool wait()
{
    F.waitForFinished();
    return true;
}

// Здесь происходит запуск потока
MQL_EXPORT int random(HWND hwnd, UINT msg, double v[], int len)
{
    if (F.isRunning()) return 0;
    F = QtConcurrent::run(fill, hwnd, msg, v, len);
    return 1;
}

Код индикатора

#property strict
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots   1
//--- plot L
#property indicator_label1  "L"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

#import "dlltest.dll"
   int random(int hwnd, uint msg, double &v[], int len);
   bool wait();
#import

//--- indicator buffers
double         L[];

int hwnd;
uint msg;

int OnInit()
{
   SetIndexBuffer(0, L);
   
   hwnd = WindowHandle(Symbol(), Period());
   msg = 0;
   Print(__LINE__);
   
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   Print(__LINE__);
   wait();
   Print(__LINE__);
}

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   Print(__LINE__);
   static int it = 0;
   int res = random(hwnd, msg, L, ArraySize(L));
   Comment(++it, " ", res);
   Print(__LINE__);
   return(rates_total);
}

И вот это все даже работает, но когда пытаюсь удалить индикатор, терминал виснет

Из OnDeinit принтов нет

В функции random идет проверка есть ли что-то в процессе или нет, если есть, то пропускаем вызов, если нет - запускаем поток на заполнение буфера

Пробовал создавать промежуточный буфер который заполняется данными в выделенном потоке и потом когда уже поток завершился генерируется событие OnTimer и внутри индикатора копируются данные обратно. Работает аналогично, т.е. данные приходят и все обновляется, но при попытке удалить индикатор терминал виснет намертво

В чем может быть проблема?

 
Mikhail Zaprudin:

Пробую идти по единственно верному пути, и что-то спотыкаюсь

Делаю DLL с иcпользованием Qt

Код индикатора

И вот это все даже работает, но когда пытаюсь удалить индикатор, терминал виснет

Из OnDeinit принтов нет

В функции random идет проверка есть ли что-то в процессе или нет, если есть, то пропускаем вызов, если нет - запускаем поток на заполнение буфера

Пробовал создавать промежуточный буфер который заполняется данными в выделенном потоке и потом когда уже поток завершился генерируется событие OnTimer и внутри индикатора копируются данные обратно. Работает аналогично, т.е. данные приходят и все обновляется, но при попытке удалить индикатор терминал виснет намертво

В чем может быть проблема?

С Qt не работал, но что-то мне подсказывает, что:

F.waitForFinished();

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

 
Mikhail Zaprudin:

Из OnDeinit принтов нет

ни перед функцией ни после? как ведет себя в дебаг режиме?

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

 
А массив разве так должен передаваться в C++? Не нужно ли заменить double v[] на double *v в реализации random в DLL? Может из-за этого память портится.
Причина обращения: