【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 ...
随机推荐
- .net core 基于 IHostedService 实现定时任务
.net core 基于 IHostedService 实现定时任务 Intro 从 .net core 2.0 开始,开始引入 IHostedService,可以通过 IHostedService ...
- springboot管理类,springboot注入类
springboot管理类,springboot注入类 定义一个配置类,添加@Configuration注解,EvaluatorTemplate代表你需要注入的第三方类 @Configuration ...
- 佳木斯集训Day4
Day4的出题人好毒瘤啊!!! T1我打表过的,正解现在也不会 #include <bits/stdc++.h> #define MAXN 10050 #define ll long lo ...
- Zookeeper开源客户端Curator的使用
开源zk客户端-Curator 创建会话: RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3); CuratorFramewor ...
- java高并发系列 - 第25天:掌握JUC中的阻塞队列
这是java高并发系列第25篇文章. 环境:jdk1.8. 本文内容 掌握Queue.BlockingQueue接口中常用的方法 介绍6中阻塞队列,及相关场景示例 重点掌握4种常用的阻塞队列 Queu ...
- HTTP与HTTPS之面试必备
本文主要讲解Http与https的区别,以及https是怎样加密来保证安全的. 首先讲这俩个协议的简单区别: HTTP:超文本传输协议. HTTPS:安全套接字层超文本传输协议HTTP+SSL HTT ...
- testng学习笔记-- 场景和module
一.定义 TestNG是一个测试框架,其灵感来自JUnit和NUnit,但引入了一些新的功能,使其功能更强大,使用更方便. TestNG是一个开源自动化测试框架;TestNG表示下一代(Next Ge ...
- JavaScript算法实现——排序
在计算机编程中,排序算法是最常用的算法之一,本文介绍了几种常见的排序算法以及它们之间的差异和复杂度. 冒泡排序 冒泡排序应该是最简单的排序算法了,在所有讲解计算机编程和数据结构的课程中,无一例外都会拿 ...
- import 和from…import
import 和from-import 一.import模块名 import time print(time.time()) import首次导入模块发生了3件事: 使用import time导入的时 ...
- 前端传递给后端复杂类型 webapi 等
来自URL:https://www.cnblogs.com/kylewell/p/5674564.html 前端我需要通过ajax 传递如下的一个json对象: var model = { Param ...