HOME / 第 6 部分 · 数据与进阶开发 / 第 69 课
MODULE 6 · 数据与进阶开发

第 69 课 · 多线程

⏱ 预计时长 35 分钟📊 难度 进阶📚 模块 第 6 部分 · 数据与进阶开发

🎯 本课学习目标

复杂计算(寻路、生成地图)卡主线程?本课用多线程处理,并学会安全通信。

📖 详细教学步骤

多线程是什么?概念

  • 主线程:处理游戏逻辑与渲染
  • 其他线程:并行跑耗时任务(文件、计算、网络)
  • 好处:不阻塞主线程,画面不卡
  • 风险:同时访问共享数据会冲突

💡 简单项目尽量少用,只有明确卡顿才用。

基础线程代码

Thread 执行一个函数:

GDSCRIPTextends Node

var thread: Thread

func _ready():
    thread = Thread.new()
    # 后台执行 heavy_calc(不阻塞主线程)
    thread.start(heavy_calc)

func heavy_calc():
    var sum = 0
    for i in range(10000000):
        sum += i
    print("后台计算完成:", sum)
    # 注意: 线程里不能直接操作场景节点!

func _exit_tree():
    if thread:
        thread.wait_to_finish()   # 退出前等待线程结束

线程传参与返回代码

传参:

GDSCRIPTthread.start(heavy_calc.bind(100, "参数"))

func heavy_calc(count: int, tag: String):
    print(tag, count)

安全回传结果(信号 + 主线程)重点

线程算完要把结果交回主线程用 call_deferred:

GDSCRIPTextends Node

var thread: Thread
var result: float = 0.0

func _ready():
    thread = Thread.new()
    thread.start(compute_pi)

func compute_pi():
    var pi = 0.0
    for i in range(1000000):
        pi += 4.0 * (1 if i % 2 == 0 else -1) / (2 * i + 1)
    # 用 call_deferred 回到主线程更新
    call_deferred("_on_done", pi)

func _on_done(pi: float):
    result = pi
    $Label.text = "π ≈ " + str(pi)   # 主线程才能改 UI
    thread.wait_to_finish()
    thread = null

注意线程安全警告

  • ❌ 线程里改场景节点/UI → 崩溃
  • ❌ 线程里读字典同时主线程写 → 竞态
  • ✅ 线程只算数据,用 call_deferred 回主线程更新
  • ✅ 或使用 Mutex/Semaphore 保护共享数据

实用场景应用

适合用多线程的:

  • 程序化生成大地图/噪声
  • 大型寻路计算
  • 文件压缩/加密
  • 网络数据解析

简单计算别用线程,开销反而更大。

⚠️
线程里操作节点崩溃?

Godot 线程不安全。所有场景/UI 操作必须回主线程(call_deferred 或信号)。

⚠️
退出时报错线程未结束?

场景退出前一定要 thread.wait_to_finish(),否则 Godot 报错。

🧪 动手试一试

🏋️ 课后练习

用线程后台计算 1 到 1 亿的和,计算时界面保持流畅(用 Label 每秒刷新当前帧数),算完用 call_deferred 显示结果。

线程算数 → call_deferred("_on_done", sum) → 主线程更新 UI。

💡

能用单线程解决就别上多线程,复杂度和 bug 会翻倍。

💡

需要共享数据时,用 Mutex(互斥锁)保护。

🎓 本课小结

多线程:Thread.start 后台跑函数,计算不阻塞主线程;结果用 call_deferred 安全回主线程更新;线程里严禁直接操作节点;退出前 wait_to_finish。