【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学习笔记(中级篇)—java实现高质量图片压缩
使用java几十行代码实现一个高质量图片压缩程序,再也不用去自己找网络的压缩程序啦!而且很多网上的工具还有水印或者其他的限制,自己动手写一个简单的应用,是再合适不过了. 一.实现原理 1.声明两个字符 ...
- Power Designer导出模型的sql加注释-Oracle语句
第一步:Database-->Edit Current DBMS 第二步: 然后分别将 Script-->Objects-->Table-->TableComment Scri ...
- mybatis学习笔记(三)
mybatis增删改 概念: 功能:从应用程序角度出发,软件具有哪些功能: 业务:完成功能时的逻辑,对应service的一个方法: 事务:从数据库角度出发,完成业务时需要执行的SQL集合,统称一个事务 ...
- Linux常用命令之压缩解压
压缩是一种通过特定的算法来减小计算机文件大小的机制.这种机制对网络用户是非常有用和高效的,因为它可以减小文件的字节总数,使文件能够通过互联网实现更快传输,此外还可以减少文件的磁盘占用空间.下面简介下z ...
- [原创实践]redhat linux 5.3搭建Nexus
1:下载安装JDK,配置好环境变量(JAVA_HOME等) 下载linux下64位的jdk-7u45-linux-x64.tar.gz(百度网盘下载,官网的jdk-7u51-linux-x64.tar ...
- CPU中的cache结构以及cache一致性
一. 引子 在多线程环境中,经常会有一些计数操作,用来统计线上服务的一些qps.平均延时.error等.为了完成这些统计,可以实现一个多线程环境下的计数器类库,方便记录和查看用户程序中的各类数值.在实 ...
- npm命令无响应
npm命令完全无反应,不是加载的那种状态 而是下标不停地在哪里闪... 之后找解决方案,说要删除npmrc文件. 强调:不是nodejs安装目录npm模块下的那个npmrc文件 而是在C:\Users ...
- net core Webapi 总目录
目录 博客目录 代码地址 博客目录 net core Webapi基础工程搭建(一)--开发工具及环境 net core Webapi基础工程搭建(二)--创建工程 net core Webapi基础 ...
- 写论文的第一天 hadoop环境搭建
毕设日志_____2019.1.23 实验集群环境搭建 三台阿里云服务器 公网ip 内网ip 120.79.63.130 node2 172.17.17.58 112.74.50.240 主节 ...
- copy好文“IT34岁危机破解心法”
在博客园中偶然发现一个好文,收藏并记录以下.在工作中更多的从企业单位用人角度去思考,或许能在职场及职业规划中更加的游刃有余,有的放矢.下面是原文. 本文题目虽是“IT人34岁危机”破解心法,但内容同样 ...