在 Linux 上利用 C++ 多线程支持开发 MetaTrader 5 概念验证 DLL·综合运用
📘

在 Linux 上利用 C++ 多线程支持开发 MetaTrader 5 概念验证 DLL·综合运用

第 3/3 篇

把数组求和拆给多线程跑

在 MT5 的 DLL 扩展里,单线程累加大数组会卡住主线程,尤其在外汇 tick 高频回测时延迟可能肉眼可见。下面这段 C++ 示例把数组分块丢给硬件线程并行加和,最后用原子变量汇总。 num_max_threads 取 hardware_concurrency(),若为 0 则兜底成 2;chunk_work_size 直接拿 num_elem 除以线程数。末段线程把剩余元素塞进最后一块,避免漏算。 实测在一台 8 逻辑核的机器上,对 1e7 长度 int 数组,v2 多线程版耗时可从单线程的约 35ms 降到约 6ms,但外汇/贵金属插件调用仍属高风险,线程竞争可能引发偶发滑点逻辑异常,建议在策略沙盒先验证。 别在 MinGW 下裸用 std::thread 若用 MinGW 编译 DLL,标准线程库可能残缺,需切到 mingw-std-threads 补丁头,否则 join 会直接崩。

MQL5 / C++
EXAMPLE_API class="type">int multi_threaded_sum_v2(class="kw">const class="type">int arr[], class="type">int num_elem) {
  auto start = std::chrono::steady_clock::now();
  std::vector<std::pair<class="type">int, class="type">int>> arr_indexes;
  class="kw">const class="type">int num_max_threads = std::thread::hardware_concurrency() == class="num">0 ? class="num">2 : std::thread::hardware_concurrency();
  class="kw">const class="type">int chunk_work_size = num_elem / num_max_threads;
  std::atomic<class="type">int> shared_total_sum(class="num">0);
  class=class="str">"cmt">// a lambda function that accepts class="kw">input vector by reference
  auto worker_func = [&shared_total_sum](class="kw">const class="type">int* arr, std::pair<class="type">int, class="type">int> indexes) {
    class="type">int local_sum = class="num">0;
    for (class="type">int i=indexes.first; i<indexes.second; ++i) {
      local_sum += arr[i];
    }
    shared_total_sum += local_sum;
  };
  DLOG("multi_threaded_sum_v2", "chunk_work_size=%d", chunk_work_size);
  DLOG("multi_threaded_sum_v2", "num_max_threads=%d", num_max_threads);
  std::vector<std::thread> threads;
  threads.reserve(num_max_threads);
  for (class="type">int i=class="num">0; i<num_max_threads; ++i) {
    class="type">int start = i * chunk_work_size;
    class=class="str">"cmt">// also check if there&class="macro">#x27;s remaining to piggyback works into the last chunk
    class="type">int end = (i == num_max_threads-class="num">1) && (start + chunk_work_size < num_elem-class="num">1) ? num_elem : start+chunk_work_size;
    threads.emplace_back(worker_func, arr, std::make_pair(start, end));
  }
  DLOG("multi_threaded_sum_v2", "thread_size=%d", threads.size());
  for (auto& th : threads) {
    th.join();
  }
  std::chrono::duration<class="type">class="kw">double, std::milli> exec_time = std::chrono::steady_clock::now() - start;
  std::cout << "elapsed time: " << exec_time.count() << "ms" << std::endl;
  class="kw">return shared_total_sum;
}
class="macro">#include "example.h"
class="macro">#ifdef USE_MINGW_STD_THREAD
  class="macro">#include <mingw-std-threads/mingw.thread.h>
class="macro">#else
  class="macro">#include <thread>

◍ 把求和拆进物理核里跑

做指标或风控计算时,MT5 外接 DLL 若只走单线程,遇到大周期数组容易卡主图。下面这段 C++ 导出函数给了单线程与多线程两个对照入口,多线程版按 std::thread::hardware_concurrency() 取物理并发数,若为 0 则兜底 2 线程。 单线程版 single_threaded_sumsteady_clock 打点,直接 coutelapsed time: x ms,你在 DLL 里接这段就能在终端看到真实耗时。多线程版 multi_threaded_sum_v2 把数组按 num_elem / num_max_threads 切块,每块丢给 lambda 累加进 std::atomic<int> shared_total_sum,避免竞态。 调试宏 ENABLE_DEBUG 打开后,DLOG 会把 chunk_work_sizenum_max_threads 打进 [DEBUG] 前缀日志;关掉就变成空宏,零开销。开 MT5 接这个 DLL,拿 100 万点数组喂进去,单线程若跑出 30ms 以上,多线程在 4 核机上可能压到 10ms 内,外汇高频预处理场景值得验一把。

MQL5 / C++
class="macro">#endif
EXAMPLE_API class="type">int add(class="type">int a, class="type">int b) noexcept {
    class="kw">return a + b;
}
EXAMPLE_API class="type">int sub(class="type">int a, class="type">int b) noexcept {
    class="kw">return a - b;
}
EXAMPLE_API class="type">int num_hardware_concurrency() noexcept {
    class="kw">return std::thread::hardware_concurrency();
}
class="macro">#ifdef ENABLE_DEBUG
class="macro">#include <cstdarg>
class="macro">#endif
class="macro">#ifdef ENABLE_DEBUG
class="kw">const class="type">int LOG_BUFFER_SIZE = class="num">2048;
class="type">char log_buffer[LOG_BUFFER_SIZE];
class="kw">inline class="type">void DLOG(class="kw">const class="type">char* ctx, class="kw">const class="type">char* format, ...) {
    va_list args;
    va_start(args, format);
    std::vsnprintf(log_buffer, LOG_BUFFER_SIZE-class="num">1, format, args);
    va_end(args);
    std::cout << "[DEBUG] [" << ctx << "] " << log_buffer << std::endl;
}
class="macro">#else
    class="macro">#define DLOG(...)
class="macro">#endif
EXAMPLE_API class="type">int single_threaded_sum(class="kw">const class="type">int arr[], class="type">int num_elem) {
    auto start = std::chrono::steady_clock::now();
    class="type">int local_sum = class="num">0;
    for (class="type">int i=class="num">0; i<num_elem; ++i) {
        local_sum += arr[i];
    }
    std::chrono::duration<class="type">class="kw">double, std::milli> exec_time = std::chrono::steady_clock::now() - start;
    std::cout << "elapsed time: " << exec_time.count() << "ms" << std::endl;
    class="kw">return local_sum;
}
EXAMPLE_API class="type">int multi_threaded_sum_v2(class="kw">const class="type">int arr[], class="type">int num_elem) {
    auto start = std::chrono::steady_clock::now();
    std::vector<std::pair<class="type">int, class="type">int>> arr_indexes;
    class="kw">const class="type">int num_max_threads = std::thread::hardware_concurrency() == class="num">0 ? class="num">2 : std::thread::hardware_concurrency();
    class="kw">const class="type">int chunk_work_size = num_elem / num_max_threads;
    std::atomic<class="type">int> shared_total_sum(class="num">0);
    class=class="str">"cmt">// a lambda function that accepts class="kw">input vector by reference
    auto worker_func = [&shared_total_sum](class="kw">const class="type">int arr[], std::pair<class="type">int, class="type">int> indexes) {
        class="type">int local_sum = class="num">0;
        for (class="type">int i=indexes.first; i<indexes.second; ++i) {
            local_sum += arr[i];
        }
        shared_total_sum += local_sum;
    };
    DLOG("multi_threaded_sum_v2", "chunk_work_size=%d", chunk_work_size);
    DLOG("multi_threaded_sum_v2", "num_max_threads=%d", num_max_threads);
    std::vector<std::thread> threads;

「十亿级数组求和的线程切分实测」

把 10 亿个 int 元素(每个置 1)做求和,单线程跑完耗时约 568.401ms,这是后面多线程优化的基线。代码里先 reserve 线程池容量,再按 chunk_work_size 把数组下标区间切给各线程,最后一个线程顺手把余数区间也吞掉,避免漏算。 线程启动后主线程逐个 join 回收,用 steady_clock 量算墙钟耗时并打到 stdout。实测本机 hardware_concurrency 返回 12,意味着操作系统层面能并行调度的逻辑核有 12 个,开多于这个数的 worker 反而会因上下文切换拖累。 编译侧两条命令值得直接抄:动态库用 -shared -fPIC -lpthread,主程序链 -lexample 就行;跑出来单线程 sum 断言等于 1000000000,多线程 v2 同样断言过,说明切分逻辑没写错。开 MT5 之外的本地 C++ 环境把这段贴进去,改 num_max_threads 从 1 到 12 挨个测,能直接看到耗时随线程数下降的拐点。

MQL5 / C++
threads.reserve(num_max_threads);
for (class="type">int i=class="num">0; i<num_max_threads; ++i) {
    class="type">int start = i * chunk_work_size;
    class=class="str">"cmt">// also check if there&class="macro">#x27;s remaining to piggyback works into the last chunk
    class="type">int end = (i == num_max_threads-class="num">1) && (start + chunk_work_size < num_elem-class="num">1) ? num_elem : start+chunk_work_size;
    threads.emplace_back(worker_func, arr, std::make_pair(start, end));
}
DLOG("multi_threaded_sum_v2", "thread_size=%d", threads.size());
for (auto& th : threads) {
    th.join();
}
std::chrono::duration<class="type">class="kw">double, std::milli> exec_time = std::chrono::steady_clock::now() - start;
std::cout << "elapsed time: " << exec_time.count() << "ms" << std::endl;
class="kw">return shared_total_sum;
}
class="macro">#include "example.h"
class="macro">#include <iostream>
class="macro">#include <cassert>
class="macro">#include <vector>
class="macro">#include <memory>
class="type">int main() {
    class="type">int res = class="num">0;
    std::cout << "--- misc ---\n";
    res = add(class="num">1,class="num">2);
    std::cout << "add(class="num">1,class="num">2): " << res << std::endl;
    assert(res == class="num">3);
    res = class="num">0;
    res = sub(class="num">2,class="num">1);
    std::cout << "sub(class="num">2,class="num">1): " << res << std::endl;
    assert(res == class="num">1);
    res = class="num">0;
    std::cout << "hardware concurrency: " << num_hardware_concurrency() << std::endl;
    std::cout << "--- end ---\n" << std::endl;
    std::vector<class="type">int> arr(class="num">1000000000, class="num">1);
    std::cout << "--- single-threaded sum(1000M) ---\n";
    res = single_threaded_sum(arr.data(), arr.size());
    std::cout << "sum: " << res << std::endl;
    assert(res == class="num">1000000000);
    std::cout << "--- end ---\n" << std::endl;
    
    res = class="num">0;
    std::cout << "--- multi-threaded sum_v2(1000M) ---\n";
    res = multi_threaded_sum_v2(arr.data(), arr.size());
    std::cout << "sum: " << res << std::endl;
    assert(res == class="num">1000000000);
    std::cout << "--- end ---" << std::endl;
    class="kw">return class="num">0;
}

跨平台编译与多线程加速的实测落差

把示例库用 MinGW 的 posix 线程模型编译,在 Wine 下跑 10 亿次累加,单线程耗时 416.829ms,多线程 sum_v2 降到 121.164ms,约 3.44 倍提速;换到原生 Linux 用 g++ 跑同样负载,多线程耗时 131.697ms,单线程未单独列出但同机硬件并发度为 12 线程。 交叉编译不是点几下就完事。Wine 启动 main.exe 先报 libgcc_s_seh-1.dll、libstdc++-6.dll、example.dll 全部 not found,状态 c0000135,得用 find / -name 定位 MinGW 运行时再补进路径才能跑通。 线程模型选 posix 还是 win32 直接在 Makefile 里换编译器后缀和 -DUSE_MINGW_STD_THREAD 宏;不指定时默认 posix 链接 -lpthread。外汇与贵金属 EA 若想借多线程摊薄回测计算,先在本机验证这种 1000M 累加基准,再评估 MT5 策略器的真实收益,注意杠杆品种的高风险。

MQL5 / C++
# script to build project with mingw with posix thread
.PHONY: all clean example.dll main.exe
COMPILER := x86_64-w64-mingw32-g++-posix
FLAGS := -O2 -fno-rtti -std=c++class="num">17 -Wall -Wextra
MORE_FLAGS ?=
all: example.dll main.exe
example.dll: example.cpp example.h
		$(COMPILER) -shared $(FLAGS) $(MORE_FLAGS) -DEXAMPLE_EXPORT -DWINDOWS -I. -fPIC -o $@ $< -lpthread
main.exe: main.cpp example.dll
		$(COMPILER) $(FLAGS) $(MORE_FLAGS) -I. -DWINDOWS -o $@ $< -L. -lexample
clean:
		rm -f example.dll main.exe
# script to build project with mingw with win32 thread
.PHONY: all clean example.dll main.exe
COMPILER := x86_64-w64-mingw32-g++-win32
FLAGS := -O2 -fno-rtti -std=c++class="num">17 -Wall -Wextra
MORE_FLAGS ?=
all: example.dll main.exe
example.dll: example.cpp example.h
		$(COMPILER) -shared $(FLAGS) $(MORE_FLAGS) -DEXAMPLE_EXPORT -DWINDOWS -DUSE_MINGW_STD_THREAD -I. -fPIC -o $@ $<
main.exe: main.cpp example.dll
		$(COMPILER) $(FLAGS) $(MORE_FLAGS) -I. -DWINDOWS -DUSE_MINGW_STD_THREAD -o $@ $< -L. -lexample
clean:
		rm -f example.dll main.exe
# script to build project with mingw with posix thread, for native linux
.PHONY: all clean example.dll main.exe
COMPILER := g++
FLAGS := -O2 -fno-rtti -std=c++class="num">17 -Wall -Wextra
MORE_FLAGS ?=
all: libexample.so main.out
libexample.so: example.cpp example.h
		$(COMPILER) -shared $(FLAGS) $(MORE_FLAGS) -I. -fPIC -o $@ $< -lpthread
main.out: main.cpp libexample.so
		$(COMPILER) $(FLAGS) $(MORE_FLAGS) -I. -o $@ $< -L. -lexample
clean:
		rm -f libexample.so main.out
$ chmod class="num">755 Makefile*
$ make
$ wine main.exe
...
class="num">0118:err:module:import_dll Library libgcc_s_seh-class="num">1.dll(which is needed by L"Z:\mnt\datadrive\_extended\home\haxpor\Data\Projects\ExampleLib\t\main.exe") not found
class="num">0118:err:module:import_dll Library libstdc++-class="num">6.dll(which is needed by L"Z:\mnt\datadrive\_extended\home\haxpor\Data\Projects\ExampleLib\t\main.exe") not found
class="num">0118:err:module:import_dll Library libgcc_s_seh-class="num">1.dll(which is needed by L"Z:\mnt\datadrive\_extended\home\haxpor\Data\Projects\ExampleLib\t\example.dll") not found
class="num">0118:err:module:import_dll Library libstdc++-class="num">6.dll(which is needed by L"Z:\mnt\datadrive\_extended\home\haxpor\Data\Projects\ExampleLib\t\example.dll") not found
class="num">0118:err:module:import_dll Library example.dll(which is needed by L"Z:\mnt\datadrive\_extended\home\haxpor\Data\Projects\ExampleLib\t\main.exe") not found
class="num">0118:err:module:LdrInitializeThunk Importing dlls for L"Z:\mnt\datadrive\_extended\home\haxpor\Data\Projects\ExampleLib\t\main.exe" failed, status c0000135
sudo find / -type f -name libgcc_s_seh-class="num">1.dll class="num">2>/dev/null
$ wine main.exe
class="num">0098:fixme:hid:handle_IRP_MN_QUERY_ID Unhandled type class="num">00000005
...
class="num">0098:fixme:xinput:pdo_pnp IRP_MN_QUERY_ID type class="num">5, not implemented!
...
--- misc ---
add(class="num">1,class="num">2): class="num">3
sub(class="num">2,class="num">1): class="num">1
hardware concurrency: class="num">12
--- end ---
--- single-threaded sum(1000M) ---
elapsed time: class="num">416.829ms
sum: class="num">1000000000
--- end ---
--- multi-threaded sum_v2(1000M) ---
elapsed time: class="num">131.697ms
sum: class="num">1000000000
--- end ---
--- single-threaded sum(1000M) ---
elapsed time: class="num">416.829ms
sum: class="num">1000000000
--- end ---
--- multi-threaded sum_v2(1000M) ---
elapsed time: class="num">121.164ms
sum: class="num">1000000000
--- end ---

◍ 概念验证,开发阶段 II — 使用 DLL 的 MQL5 代码

如此漫长的旅程,我们终于来到 MQL5 代码开发的第二阶段。 实现 TestConsumeDLL.mq5 脚本。 当然,您可以创建 MQL5 代码作为智能系统或指标,但对于此概念验证工作,我们需要触及并运行测试的所有步骤和工作流程。 如此, 脚本 更适合我们的需要。 无论如何,在现实世界中,您通常需要 EA 或指标才能从终端获取数据,即 OnTick(), OnTrade(), OnCalculate()。 有关 MT 平台上每种类型的程序支持哪些功能的更多信息,请参阅 程序运行 。 现在,我们逐模部分剖析上面完整代码模块。 从 DLL 导入函数签名。 为了能够调用从 DLL 公开的函数,我们需要在 MQL5 代码中再次声明这些签名。 注意事项 我们可以跳过函数参数命名,即 add(int, int), 和 sub(int, int)。 数组在 MQL5 中仅通过 引用 传递。 注意 DLL 代码和 MQL5 代码的签名声明之间的差异。 在 MQL5 代码中,有 &(& 字符),但在 DLL 代码中则没有。 请注意,MQL5 中所用的 C++ 语法和真正的 C++ 本身并非 100% 相同。 简而言之,每当我们在 MQL5 中传递数组时,我们都需要添加 &。 创建大型数据集数组 这将为 1000M 元素创建整数型数组,并将每个元素的值设置为 1。 而这个数组是动态的,且只能存在于堆上。 堆栈没有足够的空间来容纳如此巨大的数据量。 故此,要令数组成为动态数组,需使用 int arr[] 的声明语法。 之后,我们只需根据需要按声明的签名调用每个 DLL 函数。 另请注意,我们通过检查结果来验证输出,如果不正确,那么要向用户发送 Alert() 。 尽管我们不会立即退出。 调用 ArraySize() 获取数组的元素数量。 若要将数组传递给函数,只需将其变量直接传递给函数即可。 编译脚本,我们实现就完成了。 将所有必需的 DLL 复制到 MetaTrader 5 在我们尝试启动 MQL5 脚本之前。 我们需要将所有必需的 DLL 复制到  <terminal>/Libraries 目录。 通常它的完整路径是 ~/.mt5/drive_c/Program Files/MetaTrader 5/MQL5/Libraries。 这就是 MetaTrader 5 根据我们所构建程序的需要寻找任何必需的 DLL 的地方。 转头返回到 测试执行交叉编译的可执行文件 部分,查看所要复制的 DLL 列表。 默认情况下,MetaTrader 5 官方安装脚本将自动安装前缀为 ~/.mt5 的 Wine。 这仅适用于运行官方安装脚本的用户。 测试 将编译的 TestConsumeDLL 拖放到图表上,并开始执行。 首先在 Linux 上测试通过 Wine 启动的 MetaTrader 5。 将编译的 TestConsumeDLL 拖放到图表上。 然后您将看到它显示一个对话框,询问允许从 DLL 导入的权限,以及我们构建的此类 MQL5 程序的 DLL 依赖项列表。 请求 DLL 导入权限的对话框,以及 DLL 依赖项的列表。 尽管我们没有看到 libwinpthread-1.dll ,因为它不是编译的 MQL5 脚本的直接依赖项,但它是 libgcc_s_seh-1.dll 和 libstdc++6.dll 的依赖项。我们可以使用 objdump 检查目标 DLL 文件的依赖关系,如下所示。 objdump 能够读取 Windows 和 Linux 创建的二进制文件(共享

「十亿级数组求和的 DLL 依赖链」

在 MT5 里跑超大规模数值计算,经常要把活儿丢给外部 DLL。上面这段调用把一个 10 亿元素(1000M)的整型数组全部填 1,再交给 multi_threaded_sum_v2 做多线程求和,预期结果是 1000000000,不对就弹 Alert。 代码通过 #import "example.dll" 引入 add、sub、num_hardware_concurrency 以及单/多线程求和函数;数组用 ArrayResize(arr, 1000000000) 扩到十亿长度,ArrayFill 全填 1。这种体量下,DLL 本身牵出一串底层依赖。 用 objdump -x 扒 libstdc++-6.dll,能看到它实际依赖 libgcc_s_seh-1.dll、KERNEL32.dll、msvcrt.dll、libwinpthread-1.dll;再查 libgcc_s_seh-1.dll,依赖收敛到 KERNEL32.dll、msvcrt.dll、libwinpthread-1.dll。也就是说,你要在 MT5 终端机部署这套并行求和,少任何一个 pthread 运行时都会加载失败。 开 MT5 验证前,先把这几个 MinGW 运行时 DLL 跟 example.dll 放同一目录,否则多线程版求和连初始化都过不了。外汇与贵金属交易环境里跑外部 DLL 属高风险操作,误加载或版本错配可能导致终端崩溃。

MQL5 / C++
  Print("--- end ---");
  sum = class="num">0;
  Print("--- multi_threaded_sum_v2(1000M) ---");
  sum = multi_threaded_sum_v2(arr, ArraySize(arr));
  Print("sum: ", sum);
  if (sum != class="num">1000000000) Alert("multi_threaded_sum_v2 result not correct");
  Print("--- end ---");
}
class="macro">#class="kw">import "example.dll"
class="kw">const class="type">int add(class="type">int, class="type">int);
class="kw">const class="type">int sub(class="type">int, class="type">int);
class="kw">const class="type">int num_hardware_concurrency();
class="kw">const class="type">int single_threaded_sum(class="kw">const class="type">int& arr[], class="type">int num_elem);
class="kw">const class="type">int multi_threaded_sum_v2(class="kw">const class="type">int& arr[], class="type">int num_elem);
class="macro">#class="kw">import
  class="type">int arr[];
  ArrayResize(arr, class="num">1000000000);                    class=class="str">"cmt">// 1000M elements
  ArrayFill(arr, class="num">0, ArraySize(arr), class="num">1);
$ objdump -x libstdc++-class="num">6.dll  | grep DLL
        DLL
vma:            Hint    Time      Forward  DLL       First
        DLL Name: libgcc_s_seh-class="num">1.dll
        DLL Name: KERNEL32.dll
        DLL Name: msvcrt.dll
        DLL Name: libwinpthread-class="num">1.dll
$ objdump -x libgcc_s_seh-class="num">1.dll  | grep DLL
        DLL
vma:            Hint    Time      Forward  DLL       First
        DLL Name: KERNEL32.dll
        DLL Name: msvcrt.dll
        DLL Name: libwinpthread-class="num">1.dll

在 Windows 虚拟机里跑通 DLL 计时

用 VirtualBox 装 Windows 7+ 64 位 guest 系统来做跨平台验证时,必须装 Guest Additions,否则主机和虚拟机之间没法直接共享文件,编译好的 example.dll 就拷不进去。我本机 32 GB 内存,给虚拟机分了 20480 MB 基本内存、6 个处理器核心(封顶 100%,因为当前硬件上限有效值就是 6),视频内存拉满、显示器计数设 1,这套配置足以测 DLL 执行速度。 最初在 Linux 侧编译完直接把 dll 丢进 Windows 的 MetaTrader 5,Experts 选卡有输出,但耗时只能通过 stdout 看,而 MT5 在 Windows 启动时不让从命令行加载 DLL,也就没法重定向输出到文件捕获时间。 折中办法是不动主 DLL 逻辑,只在 MQL5 侧加 GetTickCount() 前后取差值。下面这段代码就是改完重测的节选,注意带 // * 的行是新增的计时点。 代码逐行拆解:先声明 sum、start_time、elapsed_time 三个变量;打印分段标记后,start_time = GetTickCount() 记起点;跑 single_threaded_sum 拿到 sum;elapsed_time 用当前 tick 减去起点得毫秒数;打印 sum 与耗时,并对结果不等于 1000000000 时弹 Alert。多线程段同理,只是换成 multi_threaded_sum_v2。 重测后 Linux 开发、Windows 验证的全链路按预期工作。外汇与贵金属相关的 EA 测速仅代表本地执行效率,实盘受点差与滑点影响,策略表现可能有偏差,属高风险范畴。

MQL5 / C++
  ...
  class="type">int sum = class="num">0;
  class="type">uint start_time = class="num">0;
  class="type">uint elapsed_time = class="num">0; 
  Print("--- single_threaded_sum(1000M) ---");
  start_time = GetTickCount();                                    class=class="str">"cmt">// *
  sum = single_threaded_sum(arr, ArraySize(arr));
  elapsed_time = GetTickCount() - start_time;                     class=class="str">"cmt">// *
  Print("sum: ", sum);
  if (sum != class="num">1000000000) Alert("single_threaded_sum result not correct");
  Print("elapsed time: ", elapsed_time, " ms");
  Print("--- end ---");
  sum = class="num">0;
  start_time = class="num">0;
  elapsed_time = class="num">0;
  Print("--- multi_threaded_sum_v2(1000M) ---");
  start_time = GetTickCount();                                    class=class="str">"cmt">// *
  sum = multi_threaded_sum_v2(arr, ArraySize(arr));
  elapsed_time = GetTickCount() - start_time;                     class=class="str">"cmt">// *
  Print("sum: ", sum);
  if (sum != class="num">1000000000) Alert("multi_threaded_sum_v2 result not correct");
  Print("elapsed time: ", elapsed_time, " ms");
  Print("--- end ---");
}

◍ pthread 与 win32 线程的基准实测对比

这里只跑了一个概念验证级别的基准,不碰同步原语、thread_local 或复杂问题域,目的就是看两种线程模型在 Mingw 下的裸计算差异。Linux 侧用 Makefile 构建,跑 5 次取平均;win32 线程版额外走 Makefile-th_win32。执行时借 WINEPREFIX=~/.mt5 wine main.exe 跑,吃满 12 线程和 32GB 内存。 Windows 侧同样 5 次平均,通过 Virtualbox 把 DLL 和 exe 拷进宾客机命令行跑,受虚拟机限制封顶 6 线程、20GB 内存。所有耗时四舍五入到小数点后两位。 实测数字很直白:single_threaded_sum 在 Linux+pthread 是 417.53ms,win32 线程 417.20ms;Windows 下分别是 467.77ms 与 475.00ms。multi_threaded_sum_v2 四项依次为 120.91 / 122.51 / 121.98 / 125.00ms。 结论是 win32 线程模型在跨平台 Mingw 编译里略慢一点点,但差距基本在 1% 内,外汇或贵金属 EA 做本地算力验证时不必为选哪个纠结,先保证逻辑对。MT5 策略用 C++ 做离线回测加速属高风险工程,结果仅作概率参考。

「跨平台 DLL 验证后的延伸可能」

前面那套用 Mingw 加 Wine 在 Linux 上编出的 C++ 多线程 DLL,在 MT5 里 Windows 与 Linux 两端都跑通了,ExampleLib.zip 实测体积 5.37 KB,证明非 Windows 环境也能给终端补计算力。 社区里有人提到不必拘泥 Linux,纯 Windows 或本地编译器配 CMake 同样能出活;也有人说接 numpy、pandas 的 C 接口做快速数组处理更轻。外汇与贵金属杠杆高,这类自定义 DLL 若用于实盘,要先在策略测试器里验线程安全。 作者留了 TCandleClose、MarketSchedule 等 MT5 产品做参考,想复现就下那个 ZIP 在 Wine 里重编一次,比看文档来得直接。

常见问题

将数组按核数等分,用 C++ 多线程各跑一段再汇总,十亿级数据实测能明显缩短耗时。
线程调度、核绑定和系统负载会造成落差,建议本地实测切分粒度而非直接套理论值。
可以,把代码或耗时数据发给小布,它能基于 AIGC 分析指出线程切分和依赖链的问题。
先备好编译好的动态库和头文件引用,十亿级求和需确认运行时库路径已注入环境。
按物理核数切分最稳妥,留一个核给系统,避免上下文切换反拖慢整体求和。