1. Auto.js多线程基础入门
第一次接触Auto.js多线程时,我也被各种概念搞得晕头转向。为什么需要多线程?简单来说,就像餐厅里一个服务员同时要招呼客人、传菜、收银,单线程处理会让顾客等得抓狂。多线程就是让多个"服务员"各司其职,效率自然翻倍。
在Auto.js中,所有脚本默认运行在主线程。当我们需要同时执行UI操作、网络请求和数据处理时,单线程就会遇到瓶颈。比如监控UI变化的同时还要定期请求服务器数据,单线程只能做完一件再做另一件,而多线程可以让它们并行处理。
先看个最简单的多线程例子:
// 主线程任务 console.log("主线程开始"); // 启动子线程1 threads.start(function(){ while(true){ console.log("子线程1执行中"); sleep(1000); } }); // 主线程继续执行 console.log("主线程结束");运行后会看到主线程的日志先输出,然后子线程的日志持续每秒输出。这就是多线程最基本的特性——非阻塞执行。需要注意几个关键点:
- 主线程不会等待子线程结束
- 子线程与主线程共享脚本上下文
- 所有线程默认会随脚本结束而终止
2. 多线程核心API详解
2.1 线程生命周期管理
创建线程最常用的就是threads.start(),但实际开发中我们还需要更精细的控制:
// 创建可管理的线程实例 const worker = threads.start(function(){ console.log("子线程启动"); while(!threads.currentThread().isInterrupted()){ // 执行任务 sleep(1000); } console.log("子线程结束"); }); // 等待线程启动 worker.waitFor(); // 停止线程的两种方式 worker.interrupt(); // 优雅停止 // threads.shutDownAll(); // 强制停止所有线程这里有几个实用技巧:
waitFor()确保线程真正启动后再进行后续操作isInterrupted()检查中断标志,实现优雅退出- 区别使用
interrupt()和shutDownAll()
2.2 线程间同步控制
当多个线程需要协作时,同步就变得至关重要。比如一个线程处理UI,另一个线程获取数据,数据就绪后才更新UI:
const lock = threads.lock(); let dataReady = false; // 数据线程 threads.start(function(){ // 模拟数据获取 sleep(2000); lock.lock(); try { dataReady = true; } finally { lock.unlock(); } }); // UI线程 threads.start(function(){ lock.lock(); try { while(!dataReady) { lock.wait(); } console.log("开始更新UI"); } finally { lock.unlock(); } });这种生产者-消费者模式在实际开发中非常常见。关键点:
- 使用
threads.lock()创建锁对象 - 临界区代码要用
lock()/unlock()包裹 wait()/notify()用于线程间通信
3. 复杂场景实战技巧
3.1 定时任务管理
定时器在多线程环境下需要特别注意执行上下文。比如这个电商抢购场景:
// 主线程设置抢购时间 const buyTime = new Date("2023-12-31 20:00:00").getTime(); // 监控线程 const monitor = threads.start(function(){ setInterval(function(){ const now = new Date().getTime(); if(now >= buyTime) { threads.start(function(){ // 执行抢购逻辑 console.log("开始抢购!"); }); clearInterval(this); } }, 100); }); // UI保持响应 threads.start(function(){ while(true){ // 更新倒计时UI sleep(1000); } });这种设计实现了:
- 精确到毫秒级的定时触发
- 不阻塞UI刷新
- 抢购逻辑在独立线程执行
3.2 异常处理机制
多线程环境下,异常处理需要特别设计。推荐的做法:
// 全局异常处理器 threads.setDefaultUncaughtExceptionHandler(function(e){ console.error("全局捕获:", e); }); // 带异常保护的线程 const safeThread = threads.start(function(){ try { // 业务代码 while(true){ maybeErrorOperation(); sleep(1000); } } catch(e) { console.error("线程内部捕获:", e); // 可以选择重启线程 } }); // 添加线程专属异常处理器 safeThread.setUncaughtExceptionHandler(function(t, e){ console.error(`线程${t}出错:`, e); });这种分层异常处理机制能确保:
- 线程内部异常不会导致整个脚本崩溃
- 可以针对不同线程定制处理逻辑
- 支持异常后自动恢复
4. 性能优化与调试
4.1 线程池模式
频繁创建销毁线程开销很大,可以采用线程池模式:
const threadPool = { _pool: [], _taskQueue: [], init(size) { for(let i=0; i<size; i++){ this._pool.push(this._createWorker()); } }, _createWorker() { const thread = threads.start(function(){ while(true){ const task = threads.wait(this, "newTask"); try { task.func.apply(null, task.args); } catch(e) { console.error(e); } } }); thread.isIdle = true; return thread; }, execute(func, ...args) { const idleThread = this._pool.find(t => t.isIdle); if(idleThread) { idleThread.isIdle = false; idleThread.post("newTask", {func, args}); } else { this._taskQueue.push({func, args}); } } }; // 初始化4个线程 threadPool.init(4); // 使用示例 for(let i=0; i<10; i++){ threadPool.execute(function(num){ console.log("处理任务"+num); sleep(1000); }, i); }这种实现:
- 控制最大线程数避免资源耗尽
- 任务队列保证不会丢失请求
- 线程复用减少创建开销
4.2 常见问题排查
多线程调试确实比较困难,分享几个实用技巧:
- 线程卡死检测:
function watchDog(timeout = 5000) { const timer = setTimeout(() => { console.trace("看门狗触发!"); threads.shutDownAll(); }, timeout); return { feed() { clearTimeout(timer); this.timer = setTimeout(() => { console.trace("看门狗触发!"); threads.shutDownAll(); }, timeout); } }; } // 使用示例 const dog = watchDog(); threads.start(function(){ while(true){ doWork(); dog.feed(); // 定期喂狗 sleep(1000); } });- 性能分析标记:
console.time("关键操作"); // ...执行代码 console.timeEnd("关键操作");- 线程安全检查清单:
- 共享数据是否加锁
- 是否存在竞态条件
- 异常是否妥善处理
- 资源是否及时释放