Autojs基础-多线程(threads)
1.前言我前面就提到autojs中使用ui后无法再使用阻塞函数。使用ui后ui会占用整个脚本的主线程我们为了能够使用sleep等阻塞函数可以再开个线程用于跑逻辑代码。脚本启动时会先加载ui页面然后我们设置个悬浮窗包括配置、启动等功能等配置基本信息后点击启动会开启一个线程跑逻辑代码。然后点击停止就会将逻辑线程关掉实现了脚本的配置、启动和暂停等功能。一般逻辑线程中我还会再开启一个线程来完成主要任务比如游戏脚本的自动任务功能。为什么会这样设计这样设计是为了监控一些游戏掉线等特殊情况出现如果出现这种情况可以方便关闭任务线程然后再开启一个新线程不至于出现特殊情况就会导致脚本卡死。我们可以将线程理解成河流主线程理解为主河流主河流是不允许阻塞的否则就会导致溢出而在主线程上开的分线程就可以理解成分流我们可以引出多条分流来保证同时流淌分流阻塞对主流也不会有影响。如果有分流出现问题我们可以将这个分流关闭然后再引出新的分流只要保证主流不阻塞程序就能一直跑下去。每个成熟的脚本都会有线程的内容其实功能都类似。我认为线程这部分内容是非常重要的脚本设计好线程关系能够更好保证脚本的兼容性保证脚本在特殊环境下也能正常运行。同时对于需要大量时间处理的内容我们也可以分出一个线程来处理提高脚本的运行速度。2.函数1.概况线程部分一般需要配合多个函数使用我就不按照官方文档的顺序介绍线程了。说实话官方文档对线程方面的内容是非常多的质量也很高大家可以认真看下官方对于这部分的介绍。我后面会根据自己理解简单介绍下线程的使用线程这部分在脚本开发时可以玩出花活来很多经验都是在真正脚本开发时才能学到。这部分还有关于定时器函数的介绍我们一般使用js的定时器就好了两者功能类似。我们完全没必要花时间来学习定时器的内容如果感兴趣可以自行学习。2.start、isAlive与interruptstart函数用于启动一个线程需要传递线程操作函数一个参数参数类型为函数返回启动后的线程返回类型为线程。这个启动的线程要么运行完成后自动停止要么通过中断的方式停止。因为线程之间是异步运行的我们使用这个函数时需要接收返回的线程通过返回的线程判断线程运行情况。isAlive函数用于判断线程是否存活返回线程存活情况返回类型为boolean。interrupt函数用于中断线程运行中断后立即判断线程是运行获得到的信息是还在运行但是写了代码想判断就会获取到线程已经停止。这个时间可以忽略不计但是直接获取又得到还在运行又无法获取到中断时间好在不影响最终的功能。let i 0; // 启动一个线程 let currentThread threads.start(function () { // 每500毫秒加1 for (let index 0; index 100; index) { i; sleep(500); } }); // 判断线程是否存活 let hasAlive currentThread.isAlive(); console.log(线程是否存活 hasAlive); // 当i小于100时一直循环 while (i 100) { // 当i等于10时中断线程运行强制结束循环 if (i 10) { // 中断线程运行 currentThread.interrupt(); // 强制结束循环 break; } } // 等待线程关闭 let startTime new Date().getTime(); // 中断后判断线程是否存活 hasAlive currentThread.isAlive(); console.log(中断后线程是否存活 hasAlive); // 直至线程关闭 while (currentThread.isAlive()) { } let endTime new Date().getTime(); console.log(线程已经关闭耗时 (endTime - startTime) 毫秒); // 延迟2秒看i是否增加这个值能够看判断出线程是否停止 sleep(2000); console.log(最终值 i);这个中断线程的时间大部分是0毫秒有个别时候1毫秒大家清楚就行了不会影响最后的功能。3.currentThread获取当前线程返回线程信息返回类型为线程。如果不手动开启线程脚本会运行在主线程里面我们可以将主线程关闭就会发现后面的代码无法执行了。虽然线程中断的时间可以忽略不计但是我后面的代码太简单了很可能没终止就会输出这样严谨代码最好加上等待线程中断的判断。// 获取当前线程 let currentThread threads.currentThread(); // 打印线程信息 console.log(currentThread); // 中断线程 currentThread.interrupt(); // 等待线程中断成功 while (currentThread.isAlive()) { console.log(等待线程中断...); } console.log(还能输出);4.disposable新建一个Disposable对象用于获取线程的数据主要用于线程通信返回Disposable对象返回类型Disposable。Disposable对象有setAndNotify和blockedGet两个函数。setAndNotify函数用于将数据传递给主线程并且通知主线程获取数据需要传递数据一个参数参数能够接受数字、字符串、对象等常用参数。blockedGet函数用于setAndNotify函数获取的数据返回类型对应setAndNotify函数传入类型但是传入数字返回后会变成字符串需要特别注意。blockedGet函数是一个阻塞函数不接收到数据下面的代码无法运行。说到这里除了基础用法外在多线程中可以让脚本运行到了不需要另一个线程的数据的位置在需要另一个线程数据的前面加上blockedGet函数然后在需要获取数据的线程上面随便传递个数据过去告诉计算已经完成。下面的代码就是比较两种方式获取的数据是一致的我已经用心给大家演示功能了请小伙伴们详细看下代码。let currentDisposable threads.disposable(); let i 0; // 启动一个线程 let currentThread threads.start(function () { // 每500毫秒加1 for (let index 0; index 10; index) { i; sleep(500); } // 通知主线程接收数据 currentDisposable.setAndNotify(i); }); console.log(运行了); // 获取线程中的数据 let newI currentDisposable.blockedGet(); console.log(newI); console.log(i); console.log(数据1 (newI 1)); // 将数据转换为数字 let newNumberI Number(newI); console.log(转换后数据1 (newNumberI 1)); // 线程运行结束会自动关闭 console.log(线程是否存活 currentThread.isAlive());5.atomic与lock用到线程就会考虑到线程安全问题说实话在脚本开发中真的很少用到但是这是线程的重要内容小伙伴们可以当做一个知识扩充很多编程语言也会有线程的内容线程安全其实都差不多。这两个函数都是关于线程安全的函数atomic函数用于自动创建一个数字对象然后通过调用这个对象的函数来实现增减和获取数据是安全的lock函数就是线程锁一般线程都会有这个函数在变量操作开启锁数据操作完成后关闭锁这样就能保证同一个时间多个线程只有一个线程能操作变量。atomic有非常多的函数请根据表格的介绍进行使用。基础操作get()获取原子变量的当前值。set(long newValue)设置原子变量的值为新值。算术运算 (返回新值)incrementAndGet()先自增1然后返回自增后的新值。addAndGet(long delta)先加上指定值delta然后返回相加后的新值。decrementAndGet()先自减1然后返回自减后的新值。算术运算 (返回旧值)getAndIncrement()先返回自增前的旧值然后自增1。getAndAdd(long delta)先返回相加前的旧值然后加上deltagetAndDecrement()先返回自减前的旧值然后自减1。比较与交换(CAS)compareAndSet(long expect, long update)如果当前值等于期望值expect则原子地设置为新值update。返回操作是否成功的布尔值。这是实现无锁算法的基石。值交换getAndSet(long newValue)设置为新值并返回设置之前的旧值。函数式更新updateAndGet(LongUnaryOperator op)更新当前值并返回更新后的新值。getAndUpdate(LongUnaryOperator op)更新当前值并返回更新前的旧值。lock只有lock和unlock两个函数分别代表上锁和解锁上锁时候只允许当前线程运行解锁时候允许多个线程运行。没有使用线程安全两个线程同时计算计算时加上延迟才能体现竞争关系如果不加延迟又这么少的数量没有意义。加上延迟后没有使用线程安全的情况下结果为48。let i 0; // 启动一个线程 let currentThread threads.start(function () { for (let index 0; index 25; index) { // 加1后获取新值 console.log(分线程 (i)); sleep(200); } }); for (let index 0; index 25; index) { // 加1后获取新值 console.log(主线程 (i)); sleep(200); }根据atomic函数方式实现线程安全线程安全是保证最后计算数量符合逻辑不出现重复计算但是但是数字输出先后是不保证的。let currentAtomic threads.atomic(); // 设置初始值为10 currentAtomic.set(0); // 启动一个线程 let currentThread threads.start(function () { for (let index 0; index 25; index) { // 加1后获取新值 console.log(分线程 currentAtomic.incrementAndGet()); sleep(200); } }); for (let index 0; index 25; index) { // 加1后获取新值 console.log(主线程 currentAtomic.incrementAndGet()); sleep(200); }根据lock锁的方式实现线程安全功能和automic类似更加灵活简单。let currentLock threads.lock(); let i 0; // 启动一个线程 let currentThread threads.start(function () { for (let index 0; index 25; index) { // 上锁 currentLock.lock(); // 加1后获取新值 console.log(分线程 (i)); // 解锁 currentLock.unlock(); sleep(200); } }); for (let index 0; index 25; index) { // 上锁 currentLock.lock(); // 加1后获取新值 console.log(主线程 (i)); // 解锁 currentLock.unlock(); sleep(200); }6.join等待线程运行完成再运行后面的脚本。let i 0; // 启动一个线程 let currentThread threads.start(function () { for (let index 0; index 25; index) { // 加1后获取新值 console.log(分线程 (i)); sleep(200); } }); // 等待线程运行完成 currentThread.join(); for (let index 0; index 25; index) { // 加1后获取新值 console.log(主线程 (i)); sleep(200); }可以看出当分线程运行结束后主线程才会运行。7.waitFor线程执行需要一定时间这个函数用于线程开始执行时运行后面的脚本。这个函数的使用场景我一时真无法想起可以特殊情况下使用吧。线程启动起来应该花费不了多少时间我感觉这个函数用处不大。let i 0; // 启动一个线程 let currentThread threads.start(function () { for (let index 0; index 25; index) { // 加1后获取新值 console.log(分线程 (i)); sleep(200); } }); // 等待线程开始运行 currentThread.waitFor(); for (let index 0; index 25; index) { // 加1后获取新值 console.log(主线程 (i)); sleep(200); }8.sync将函数变成同步函数需要传递函数一个参数参数类型为函数返回同步函数返回类型为函数。变成同步函数的作用是同一时间只有一个线程能调用这个函数也能实现线程安全加上上面介绍的automic以及lock函数一共有三种方式可以实现线程安全。我为了节省时间只运行了50次哪怕加上延迟也会有概率不触发线程竞争问题。我现在就不保证线程安全的情况下在每个线程中累加1000次。let i 0; function add(x) { i x; } // 启动一个线程 let currentThread threads.start(function () { for (let index 0; index 1000; index) { add(1); } }); for (let index 0; index 1000; index) { add(1); } // 等待线程运行完成 currentThread.join(); console.log(i);正常应该得到2000的结果然而最终得到了1656差距还是很大的。通过sync函数将累加函数变成同步函数后能够得到正常的数据。let i 0; function add(x) { i x; } // 获取异步函数 let syncAdd sync(add); // 启动一个线程 let currentThread threads.start(function () { for (let index 0; index 1000; index) { syncAdd(1); } }); for (let index 0; index 1000; index) { syncAdd(1); } // 等待线程运行完成 currentThread.join(); console.log(i);