【JavaScript】吃饱了撑的系列之JavaScript模拟多线程并发
github地址
本文的目的
场景一
场景二
github地址
https://github.com/penghuwan/concurrent-thread.jsgithub.com
concurrent-thread-js功能简介
为什么不选用webworker实现?
什么时候使用concurrent-thread-js
API总览
- submit(function,[namespace]): 接收一个函数,普通函数或Async函数均可,并异步执行"线程"
- sleep(ms): "线程"休眠,可指定休眠时间ms,以毫秒计算
- join(threadName): "线程"同步,调用此方法的"线程"函数将在threadName执行结束后继续执行
- interupt(threadName): "线程"中断,影响"线程"内部调this.isInterrupted()的返回值
- Lock.lock: 加锁,一个时刻只能有一个"线程"函数进入临界区,其他"线程"函数需要等待,锁是非公平的,也就是说后面排队的线程函数没有先后,以随机的方式进行竞争。
- Lock.unlock:解除非公平锁
- Condition.wait:不具备执行条件,"线程"进入waiting状态,等待被唤醒
- Condition.notify:随机唤醒一个wait的"线程"
- Condition.notifyAll: 尚未编写,唤醒所有wait的"线程"
- getState: 还没写完 获取"线程"状态,包括RUNNALE(运行),WAITING(等待),BLOCKED(阻塞),TERMINATED(终止)
- ThreadPool类:包含submit/sleep/join/interrupt/getState方法
- Lock类:包含Lock.lock和Lock.unLock方法
- Condition类:包含Condition.wait和Condition.notify方法
A1.submit方法
// 备注:为循序渐进介绍,以下为简化代码
// 存储每个线程函数的状态,例如是否中断,以及线程状态等
const threadMap = {}; class ThreadPool {
// 模拟线程中断
interrupt(threadName) { }
// 模拟线程同步
join(threadName, targetThread) { }
// 模拟线程休眠
sleep(ms) { }
};
function submit(func, name) {
if (!func instanceof Function) return;
// 方式1:传入一个具名函数;方式2:传入第二个参数,即线程命名空间
const threadName = func.name || name;
// threadMap负责存储线程状态数据
threadMap[threadName] = { state: RUNNABLE, isInterrupted: false };
// 让func异步调用,同时将传入函数的作用域绑定为 ThreadPool原型
Promise.resolve({
then: func.bind(ThreadPool.prototype);
})
}
- 获取线程函数的命名空间,并初始化线程初始数据,不同线程状态由threadMap全局存储
- 将提交的函数func作为Promise.resolve方法中的一个thenable对象的then参数,这相当于立即"完成"一个Promise,同时在then方法中执行func,func会以异步而不是同步的方式进行执行,你也可以简单的理解成类似于执行了setTimeOut(func,0);
- 将func的作用域绑定为新生成的ThreadPool实例,ThreadPool中定义了我们上面我们介绍到的方法,如sleep/join/interupt等,这有什么好处呢?这意味着我们可以直接在函数中通过调用this.interrupt的方式去调用我们定义的API了,符合我们的使用习惯(注意,class中定义的除箭头函数外的普通函数实际上都存放在原型中)
submit(async function example() {
this.interrupt();
});
submit(async function example() {
this.interrupt('example');
});
// 返回代理后的ThreadPool
function delegateThreadPool(threadName) { // threadName为待定的线程名,在submit方法调用时候传入
// 代理后的ThreadPool
const proxyClass = {};
// 获取ThreadPool原来的所有的方法,赋给props数组
var props = Object.getOwnPropertyNames(ThreadPool.prototype);
for (let prop of props) {
// 代理ThreadPool,为其所有方法增加threadName这个参数
let fnName = prop;
proxyClass[fnName] = (...args) => {
const fn = baseClass[fnName];
return fn(threadName, ...args);
};
}
return proxyClass;
}
function submit(func, name) {
// 省略其他代码 。。。
const proxyScope = delegateThreadPool(threadName);
// 让func异步调用,不阻塞主线程,同时实现并发
Promise.resolve({
then: function () {
// 给func绑定this为代理后的ThreadPool对象,以便调用方法
func.call(proxyScope);
}
});
}
// 调用this.sleep方法时,已经无需增加函数命名作为参数了
submit(async function example() {
this.interrupt();
});
A2. sleep方法
// 模拟“线程”休眠
sleep(ms) {
return new Promise(function (resolve) {
setTimeout(resolve, ms);
})
}
// 提交“线程”
submit(async function example() {
// 阻塞停留3秒,然后才输出1
await this.sleep(3000);
console.log(1);
});
A3. interrupt方法
// 模拟线程中断
interrupt(threadName) {
if (!threadName) { throw new Error('Miss function parameters') }
if (threadMap[threadName]) {
threadMap[threadName].isInterrupted = true;
}
}
// 获取线程中断状态
isInterrupted(threadName) {
if (!threadName) { throw new Error('Miss function parameters') }
// !!的作用是:将undefined转为false
return !!threadMap[threadName].isInterrupted;
}
import ee from 'event-emitter';
const emitter = ee();
// 模拟线程同步
join(threadName, targetThread) {
return new Promise((resolve) => {
// 监听其他线程函数的结束事件
emitter.on('join-finished', (finishThread) => {
// 根据结束线程的线程名finishThread做判断
if (finishThread === targetThread) {
resolve();
}
})
})
}
import ee from 'event-emitter';
const emitter = ee();
function submit(func, name) {
// ...
Promise.resolve({
then: func().then(() => {
emitter.emit('join-finished', threadName);
})
});
}
submit(async function thread1 () {
this.join('thread2');
console.log(1);
});
submit(async function thread2 () {
this.sleep(3000);
console.log(2)
})
// 3s后,依次输出 2 1
A5. Lock.lock & Lock.unlock(非公平锁)
- lock方法:lock方法首先会判断isLock是否为false,如果是,则代表没有线程占领临界区,那么允许该线程进入临界区,同时把isLock设置为true,不允许其他线程函数进入。其他线程进入时,由于判断isLock为true,会setTimeOut每隔一段时间递归调用判断isLock是否为false,从而以较低性能消耗的方式模拟while死循环。当它们检测到isLock为false时候,则会进入临界区,同时设置isLock为true。因为后面的线程没有先后顺序,所以这是一个非公平锁
- unLock方法:unlock则是把isLock属性设置为false,解除锁定就可以了
// 这是一个非公平锁
class Lock {
constructor() {
this.isLock = false;
}
//加锁
lock() {
if (this.isLock) {
const self = this;
// 循环while死循环,不停测试isLock是否等于false
return new Promise((resolve) => {
(function recursion() {
if (!self.isLock) {
// 占用锁
self.isLock = true;
// 使外部await语句继续往下执行
resolve();
return;
}
setTimeout(recursion, 100);
})();
});
} else {
this.isLock = true;
return Promise.resolve();
}
}
// 解锁
unLock() {
this.isLock = false;
}
}
const lockObj = new Lock();
export default lockObj;
async function commonCode() {
await Lock.lock();
await Executor.sleep(3000);
Lock.unLock();
} submit(async function example1() {
console.log('example1 start')
await commonCode();
console.log('example1 end')
});
submit(async function example2() {
console.log('example2 start')
await commonCode();
console.log('example2 end')
});
// 立即输出
example1 start
example2 start
// 3秒后输出
example1 end
// 再3秒后输出
example2 end
A6. Condition.wait & Condition.notify(条件变量)
- Condition.wait:不具备执行条件,线程进入waiting状态,等待被唤醒
- Condition.notify: 唤醒线程
import ee from 'event-emitter';
const ev = ee(); class Condition {
constructor() {
this.n = 0;
this.list = [];
}
// 当不满足条件时,让线程处于等待状态
wait() {
return new Promise((resolve) => {
const eventName = `notify-${this.n}`;
this.n++;
const list = this.list;
list.push(eventName);
ev.on(eventName, () => {
// 从列表中删除事件名
const i = list.indexOf(eventName);
list.splice(i, 1);
// 让外部函数恢复执行
debugger;
resolve();
})
})
}
// 选择一个线程唤醒
notify() {
const list = this.list;
let i = Math.random() * (this.list.length - 1);
i = Math.floor(i);
ev.emit(list[i])
}
}
async function testCode() {
console.log('i will be wait');
if (true) {
await Condition.wait();
};
console.log('i was notified ');
} submit(async function example() {
testCode();
setTimeout(() => {
Condition.notify();
}, 3000);
});
i will be wait
// 3秒后输出
i was notified
最后的大总结
- 你想让一段代码停一下?OK!写个返回Promise的函数,用await修饰,它就停啦!
- 你想控制它(await)不要停了,继续往下走?OK! 把Promise给resolve掉,它就往下走啦
- 你说你不知道怎么控制它停,因为监听和发射事件的代码分布在两个地方?OK!那就使用事件流
【JavaScript】吃饱了撑的系列之JavaScript模拟多线程并发的更多相关文章
- 大半夜吃饱了撑的帮人调IE玩
那高手的也是IE6,我也是IE6,但是他的IE6就总是进recv,我的IE6就进WSARecv,一点都不科学...擦..不调了.
- Java基础系列篇:JAVA多线程 并发编程
一:为什么要用多线程: 我相信所有的东西都是以实际使用价值而去学习的,没有实际价值的学习,学了没用,没用就不会学的好. 多线程也是一样,以前学习java并没有觉得多线程有多了不起,不用多线程我一样可以 ...
- javascript动画系列第一篇——模拟拖拽
× 目录 [1]原理介绍 [2]代码实现 [3]代码优化[4]拖拽冲突[5]IE兼容 前面的话 从本文开始,介绍javascript动画系列.javascript本身是具有原生拖放功能的,但是由于兼容 ...
- javaScript系列 [06]-javaScript和this
在javaScript系列 [01]-javaScript函数基础这篇文章中我已经简单介绍了JavaScript语言在函数使用中this的指向问题,虽然篇幅不长,但其实最重要的部分已经讲清楚了,这篇文 ...
- javaScript系列 [03]-javaScript原型对象
[03]-javaScript原型对象 引用: javaScript是一门基于原型的语言,它允许对象通过原型链引用另一个对象来构建对象中的复杂性,JavaScript使用原型链这种机制来实现动态代理. ...
- JavaScript中的数据结构及实战系列
本系列主要是讲解JavaScript中的数据结构及在实际项目中遇到的地方 JavaScript中的数据结构及实战系列(1):队列 JavaScript中的数据结构及实战系列(2):栈
- (一)我的Javascript系列:Javascript的面向对象旅程(上)
今宵酒醒何处,杨柳岸,晓风残月 导引 我的JavaScript系列文章是我自己对JavaScript语言的感悟所撰写的系列文章.现在还没有写完.目前一共出了下面的系列: (三)我的JavaScript ...
- 浅谈系列之 javascript原型与对象
在我学习与使用javascript三个月中,我一直对javascript的继承关系以及prototype理解不清,导致很多时候为什么这么用说不出个所以然来.截止到本周为止,通过之前的学习以及自己的再学 ...
- 3 HTML&JS等前端知识系列之javascript的基础
preface 作为一名运维开发,必须懂得前端知识,比如javascript,dom等等,下面就聊聊javascript. include 数据格式 条件判断,循环流程等. 函数 面向对象 what ...
随机推荐
- Java 获取操作系统相关的内容
package com.hikvision.discsetup.util; import java.lang.reflect.Field; import java.net.InetAddress; i ...
- Dialog 使用详解
极力推荐文章:欢迎收藏 Android 干货分享 阅读五分钟,每日十点,和您一起终身学习,这里是程序员Android 本篇文章主要介绍 Android 开发中的部分知识点,通过阅读本篇文章,您将收获以 ...
- c#图片的平移与旋转
1新建文件夹,添加一个图片 2 添加控件 两个button控件 一个image控件 一个Canvas控件 3 代码实现 using System;using System.Collections.Ge ...
- Prometheus 集成 Node Exporter
文章首发于公众号<程序员果果> 地址:https://mp.weixin.qq.com/s/40ULB9UWbXVA21MxqnjBxw 简介 Prometheus 官方和一些第三方,已经 ...
- Nunit与Xunit介绍
Nunit安装 首先说下,nunit2.X与3.X版本需要安装不同的vs扩展. nunit2.x安装 安装如上3个,辅助创建nunit测试项目与在vs中运行单元测试用例 . 1.Nunit2 Test ...
- maven阿里云镜像setting
<?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://mav ...
- Mysql: 图解 inner join、left join、right join、full outer join、union、union all的区别
转载来源 对于SQL的Join,在学习起来可能是比较乱的.我们知道,SQL的Join语法有很多inner的,有outer的,有left的,有时候,对于Select出来的结果集是什么样子有点不是很清楚. ...
- 欢迎加入我的知识星球:C语言解惑课堂
我在知识星球上开通了一个有关C语言基础答疑解惑的星球,它叫做:“C语言解惑课堂”.看这名字你就知道虽然有点俗,俗才贴近你的真正需求嘛!这是一个专门帮助C语言初学者答疑解惑的课堂.嗯~~~,关于这个星球 ...
- 在vps上安装 kali linux
在渗透测试过程中,很多时候我们需要反弹一个shell回来.使用empire也好,MSF也好,其他工具也好,都避不开公网IP的问题.这时候我们就需要一个VPS来进一步进行渗透测试. 建立通道连接的方式有 ...
- MonkeyRunner 第一天
1.安装集成Android SDK的环境(如Eclipse),主要是为了android的模拟器,安装python编译环境,MonkeyRunner是基于Jython 2.使用Eclipse打开Andr ...