让策略程序真正并发执行,给JavaScript策略增加系统底层多线程支持

Author: 小小梦, Created: 2023-03-02 14:19:15, Updated: 2024-03-20 15:06:41

img

让策略程序真正并发执行,给JavaScript策略增加系统底层多线程支持

在使用JavaScript语言在FMZ上开发策略时,由于策略架构是轮询的。如果有并发设计的场景是通过exchange.Go函数来进行一些接口的并发调用,从而实现一些并发场景的需求。但是如果希望真正单独创建一个线程执行一系列的操作是无法实现的,例如像Python语言一样,使用threading库去做一些并发设计。

基于这种需求,FMZ平台升级了系统底层。给JavaScript语言也增加了真正的多线程支持。详细的功能包括:

  • 创建线程并发执行自定义的函数。
  • 线程间通信。
  • 共享线程间储存的变量。
  • 等待线程执行结束回收资源并返回执行结果。
  • 强制结束线程,并回收资源。
  • 在并发的线程执行函数中获取当前线程ID。

接下来小编带您一起逐个理解每一项功能。

创建线程并发执行自定义的函数

__Thread函数可以创建一个线程,并发执行一个函数。例如需要创建一个并发的函数func1func1函数做什么工作呢?我们可以让它从0累加到9,为了看到这个逐步累加的过程,在func1函数中使用for循环每次暂停(Sleep函数用来休眠一定毫秒数)一定时间。

function func1(sleepMilliseconds) {
    var sum = 0 
    for (var i = 0 ; i < 10 ; i++) {
        sum += i 
        Sleep(sleepMilliseconds)
        Log("sum:", sum)
    }
    
    return sum
}

function main() {
    // 使用__Thread函数并发创建一个线程,参数200即为func1函数的参数,
    // 如果func1函数有多个参数,这里就具体传对应的参数
    var thread1Id = __Thread(func1, 200)
    
    // 这里需要等待线程Id为thread1Id的线程执行结果,否则main函数执行完就直接释放所有线程
    var ret = __threadJoin(thread1Id)
    Log("ret:", ret)
}

实际应用场景中,我们可以这样并发进行http请求:

function main() {
    let threads = [
        "https://www.baidu.com",
        "https://www.163.com"
    ].map(function(url) {
        return __Thread(function(url) {
            Log("GET", url)
            return HttpQuery(url)
        }, url)
    })
    threads.forEach(function(tid) {
        Log(__threadJoin(tid))
    })
}

等待线程执行结束回收资源并返回执行结果

以上例子中我们在main函数中最后使用了__threadJoin函数来等待并发的线程执行完毕,变量ret接收__threadJoin函数的返回值,我们打印了这个返回值,可以观察这个并发的线程执行的具体结果。

// id:线程ID,terminated:是否被强制停止,elapsed:耗时(纳秒),ret:线程执行函数的返回值
ret: {"id":1,"terminated":false,"elapsed":2004884301,"ret":45}

强制结束线程,并回收资源

function func1(sleepMilliseconds) {
    var sum = 0 
    for (var i = 0 ; i < 10 ; i++) {
        sum += i 
        Sleep(sleepMilliseconds)
        Log("sum:", sum)
    }
    
    return sum
}

function main() {
    var thread1Id = __Thread(func1, 200)
    Sleep(1000)
    retThreadTerminate = __threadTerminate(thread1Id)
    Log(retThreadTerminate)   // true
}

还是以刚才的例子,在创建线程后,可以在等待1秒之后就强制终止线程执行。

线程间通信

线程间通信主要使用__threadPostMessage函数和__threadPeekMessage函数。我们来看以下简单例子:

function func1() {
    var id = __threadId()
    while (true) {
        var postMsg = "来自id:" + id + "的线程函数func1的消息"
        __threadPostMessage(0, postMsg)              // 发送消息到主线程
        var peekMsg = __threadPeekMessage()         // 接收来自其它线程的消息
        Log(peekMsg)
        Sleep(5000)
    }
}

function main() {
    var threadId = __Thread(func1)
    
    while (true) {
        var postMsg = "来自主线程的main函数的消息"
        __threadPostMessage(threadId, postMsg)
        var peekMsg = __threadPeekMessage()
        Log(peekMsg, "#FF0000")                     // #FF0000 , 设置日志为红色用于区分
        Sleep(5000)
    }
}

__threadPostMessage函数用于向某个线程发送消息,第一个参数是具体发送到哪个线程的ID,第二个参数是发送的消息,可以是字符串、数值、数组、JSON对象等。可以在并发的线程函数中向主线程发送消息,主线程的ID定义为0。

__threadPeekMessage函数用于监听某个线程发送来的消息, 可以设置超时时间(毫秒数),也可以设置为0表示阻塞,一直监听到有消息才返回。

当然,除了并发的线程和主线程通信。并发的线程之间也可以直接相互通信。

在并发的线程执行函数中获取当前线程ID

在上面的例子中,使用了var id = __threadId()__threadId()函数可以获取当前线程的ID。

共享线程间储存的变量

除了线程间的通信,还可以使用共享变量进行交互。

function testFunc() {
    __threadSetData(0, "testFunc", 100)   // 储存在当前线程环境,键值对 testFunc : 100 
    Log("testFunc执行完毕")
}

function main() {
    // threadId为1 ,创建的threadId为1的线程会先执行完,只要线程资源没有被回收,线程本地储存的变量就有效
    var testThread = __Thread(testFunc)
    
    Sleep(1000)

    // 输出 in main, get testFunc: 100
    Log("in main, get testFunc:", __threadGetData(testThread, "testFunc"))   // 取出键名为testFunc的值
}

以上就是所有功能的简单演示。


Related

More

骨刀 复制这个策略运行提示: 错误 ReferenceError: '__Thread' is not defined at main (__FILE__:5)

骨刀 收藏学习

发明者量化 升级托管者