【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 ...
随机推荐
- spring boot 学习笔记(二)之打包
一.叙述 spring boot 在 pom 中可以配置成 packaging 为 jar ,这样打包出来的就是一个 jar 包,可以通过 Java 命令直接运行, Java 命令为: java - ...
- Kalman Filter、Extended Kalman Filter以及Unscented Kalman Filter介绍
模型定义 如上图所示,卡尔曼滤波(Kalman Filter)的基本模型和隐马尔可夫模型类似,不同的是隐马尔科夫模型考虑离散的状态空间,而卡尔曼滤波的状态空间以及观测空间都是连续的,并且都属于高斯分布 ...
- oracle 创建表空间,用户并授权
1. 查看所有表空间及存储路径 select file_name, tablespace_name from dba_data_files; 2. 创建表空间 CREATE TABLESPACE xs ...
- Java学习多线程第一天
内容介绍 Thread 线程创建 线程池 线程状态图 1 多线程 1.1 多线程介绍 学习多线程之前,我们先要了解几个关于多线程有关的概念. 进程:进程指正在运行的程序.确切的来说,当一个程序 ...
- 渐进式web应用开发---Service Worker 与页面通信(七)
_ 阅读目录 一:页面窗口向 service worker 通信 二:service worker 向所有打开的窗口页面通信 三:service worker 向特定的窗口通信 四:学习 Messag ...
- python编写环境(种类)
python编写环境(种类) 官方推荐 cpython 转成C的字节码 jython转成Java的字节码 ironpython转成C#字节码 pypy转换成动态编译 开发快,运行快
- python 之 前端开发(基本选择器、组合选择器、 交集与并集选择器、序列选择器、属性选择器、伪类选择器、伪元素选择器)
11.3 css 11.31 基本选择器 11.311 id选择器 根据指定的id名称,在当前界面中找到对应的唯一一个的标签,然后设置属性 <!DOCTYPE html> <html ...
- 100天搞定机器学习|Day36用有趣的方式解释梯度下降算法
本文为3Blue1Brown神经网络课程讲解第二部分<Gradient descent, how neural networks learn >的学习笔记,观看地址:www.bilibil ...
- C语言连接mysql,用GCC编译
1. main.c文件内容如下 #include <stdlib.h>#include <stdio.h>#include <winsock.h>#include ...
- SpringDataJpa在一对多、多对多关系映射时出现StackOverflowError
在使用spring-data-jpa时,进行一对多配置后,在调用save方法时,出现内存溢出. 产生原因一:为了方便看信息,在两类中分别重写了 toString 方法,导致查询加载时两类在互相调用对方 ...