这篇文章根据我实测的两个 H 版多线程示例整理,重点不是“开线程越多越快”,而是把 线程怎么启动、函数怎么调用、对象怎么传、共享数组怎么写 这几件事讲清楚。相关命令可以先看 H 版帮助文档里的 AhkThread()CriticalObject(),以及总入口 AHK_H 帮助文档

先看最小线程

AHK_H 的 AhkThread() 是在当前进程里创建新的 AutoHotkey 线程。它和 Run 一个新脚本不是一回事:线程之间可以共享进程内对象,也可以通过 H 版对象方法互相调用函数。

#Requires AutoHotkey v1.1
#NoEnv
SetBatchLines, -1

t1 := AhkThread("MsgBox, 子线程 1")
t2 := AhkThread("MsgBox, 子线程 2")

MsgBox, 主线程也在运行。点掉这个窗口后,再看两个子线程窗口。

ahkthread_free(t1), t1 := ""
ahkthread_free(t2), t2 := ""

对象怎么传

实测示例里一个很容易踩坑的点是:把对象地址传给新线程时,&obj 后面最好拼一个空字符串,让它稳定按字符串参数传进去;在线程里再用 Object(ptr) 还原。不要在调用参数里直接写 Object(obj) 当中介。

threadCode =
(
#Persistent
ReadObj(ptr) {
    obj := Object(ptr)
    MsgBox, % "子线程读取:" obj["a", "b"]
    obj["a", "b"] := "str2"
}
)

obj := []
obj["a", "b"] := "str1"

t := AhkThread(threadCode)
t.ahkFunction("ReadObj", &obj "")

MsgBox, % "主线程看到:" obj["a", "b"]

共享数组不要硬怼

多个线程都往同一个数组里频繁 Push(),锁竞争会很明显。实测更推荐:每个线程先写自己的临时数组,最后再把整块结果 Push 到共享对象。这样锁次数少,结构也更清楚。

threadCode =
(
#Persistent
WriteChunk(sharedPtr, count) {
    shared := CriticalObject(sharedPtr)
    temp := []
    Loop %count%
        temp[A_Index] := "Value " A_Index
    shared.Push(temp)
    ExitApp
}
)

threadCount := 4
itemCount := 40000
shared := CriticalObject()

Loop %threadCount% {
    th%A_Index% := AhkThread(threadCode)
    th%A_Index%.ahkPostFunction("WriteChunk", &shared "", itemCount // threadCount)
}

Loop {
    ready := ""
    Loop %threadCount%
        ready .= th%A_Index%.ahkReady()
    if !ready
        break
    Sleep, 50
}

MsgBox, % "线程块数:" shared.MaxIndex() "`n第一块数量:" shared[1].MaxIndex()

ahkFunction 与 ahkPostFunction

  • ahkFunction() 会等待线程里的函数执行完,适合需要返回值的场景。它更像同步调用。
  • ahkPostFunction() 发出调用后马上返回,适合异步任务。它的返回值主要表示调用是否投递成功,不适合当成业务结果。
  • 线程代码里如果要反复接收调用,通常需要 #Persistent。否则线程可能已经退出,后面的函数调用就会失败。
  • 传参优先按字符串、数字理解。对象、数组这类复杂数据要用指针和 Object() 还原。
threadCode =
(
#Persistent
GetText(name) {
    return "来自子线程的返回值:" name
}
DoWork(name) {
    MsgBox, % "后台任务收到:" name
}
)

t := AhkThread(threadCode)

; 同步调用:会等待,并拿到返回值
MsgBox, % t.ahkFunction("GetText", "task-1")

; 异步调用:马上返回,业务结果要用其它方式保存
ret := t.ahkPostFunction("DoWork", "task-2")
MsgBox, % "投递结果:" ret

实战建议

  • 小任务不要强行多线程,创建线程、同步数据也有成本。
  • CPU 密集型任务可以按核心数切块,不要无脑开几十个线程。
  • 共享对象适合传状态、传结果,不适合所有线程都高频修改同一个字符串。
  • 异步任务要设计完成标志,例如 obj._t1_status := 1,不要只靠固定 Sleep 猜时间。
  • 测试脚本里可以暴力关进程,但正式脚本应优先 ExitAppahkTerminate()ahkthread_free() 这类可控释放方式。
声明:站内资源为整理优化好的代码上传分享与学习研究,如果是开源代码基本都会标明出处,方便大家扩展学习路径。请不要恶意搬运,破坏站长辛苦整理维护的劳动成果。本站为爱好者分享站点,所有内容不作为商业行为。如若本站上传内容侵犯了原著者的合法权益,请联系我们进行删除下架。