1. 操作系统基础概念
-
程序:
Program
, 可供CPU
执行的代码, 存储在外存中, Ex:.exe
-- 静止的 -
进程:
Progress
, 把程序从外存中调入内存, 分配必需数据和可执行代码段 -> 进程是程序的可执行状态 -- 活动的 -
线程:
Thread
, 线程是进程内部执行代码的基本单位 -
试题: 进程和线程间的关系
-
进程是操作系统分配内存的基本单位
-
线程是
CPU
执行代码的基本单位 -
线程处于进程内部, 一个进程中至少有一个线程, 也可以同时存在多个线程
-
每个线程也需要自己独立的内存空间, 至少 2
MB
-
操作系统中多个线程在宏观上看是 "同时执行", 微观上看是 "依次循环执行" -- 并发执行
-
-
试题:
chrome
浏览器中的线程模型是怎样的?-
发起
HTTP
请求使用 6 个并发线程 -- 请求线程 -
执行所有代码(
HTML
/CSS
/JS
...... ) 使用一个线程 --UI
渲染主线程
-
-
页面中执行一段耗时的
JS
任务, 出现的现象<button>按钮1</button> <script type="text/javascript" src="xxx.js"></script> <button>按钮2</button>
- 若
xxx.js
不退出, 则按钮 2 不可见, 按钮 1 可见但没有事件处理 - 产生原因: 浏览器中执行代码只使用 1 个
UI
主线程 - 解决方法: 创建一个并发线程, 让它去执行耗时的
JS
任务,UI
主线程仅负责页面渲染和事件监听
- 若
2. Web Worker
2.1. 创建新的执行线程的方法
- 语法:
new Worker('xxx.js')
2.2. Worker 线程致命的缺陷
- 浏览器不允许
Web Worker
线程使用任务DOM
&BOM
对象 -> 浏览器只允许UI
主线程使用DOM
&BOM
对象
2.3. 让 Worker 线程给 UI 主线程发消息
-
UI
主线程:let myWorker = new Worker('xxx.js') myWorker.addEventListener('message', function (e) { e.data }) // 还可以写成 myWorker.onmessage = function (e) { e.data }
-
Worker
线程...... postMessage('stringMsg')
-
Ex:
<body> <h3>Worker 线程给 UI 主线程发消息</h3> <button class="btnOne">按钮1</button> <script type="text/javascript"> let myWorker = new Worker('workerTest.js') myWorker.addEventListener('message', function (e) { console.log(`UI 主线程接受到消息:${e.data}`) }) </script> <button class="btnTwo">按钮2</button> </body>
// workTest.js /* Worker 线程给 UI 主线程发消息 */ console.time('质数计算') let num = 9999999999999 let result = isPrime(num) console.timeEnd('质数计算') postMessage(`${num}是质数吗? ${result}`) function isPrime(num) { // 模拟出耗时 5 s+ 的效果 let start = new Date().getTime() do { var now = new Date().getTime() } while(5000 >= now - start) // JS 中的最大整数只能到十位 num = parseInt(num) for(var i = 2;i < num; i++) { if (0 === num % i) { break } } if (i < num) { return false } else { return true } }
2.4. 让 UI 主线程给 Worker 线程发消息
-
UI
主线程let myWorker = new Worker('xxx.js') myWorker.postMessage('stringMsg')
-
Worker
线程...... onmessage = function (e) { e.data }
-
Ex:
<style> span.isPrimeBtn{ display: inline-block; padding: 0 15px; height: 25px; line-height: 25px; background: #2F4056; color: #fff; cursor: pointer; } </style> <body> <h3>UI 主线程给 Worker 线程发消息</h3> <button class="btnOne">按钮1</button> <input type="text" name="txtNumber" /> <span class="isPrimeBtn">开始质数判断</span> <button class="btnTwo">按钮2</button> <script type="text/javascript"> let isPrimeBtn = document.body.getElementsByClassName('isPrimeBtn')[0] isPrimeBtn.addEventListener('click', function () { let num = document.getElementsByName('txtNumber')[0].value // 创建 Worker 线程执行耗时任务 let myWorker = new Worker('workerMsg.js') myWorker.postMessage(num) }) </script> </body>
// workerMsg.js /* Worker 线程给 UI 主线程发消息 */ onmessage = function (e) { let num = e.data if (!num) { console.log('请输入值后在判断!') return false } console.time('质数计算') let result = isPrime(num) console.timeEnd('质数计算') console.log(`${num}是质数吗? ${result}`) } function isPrime(num) { // 模拟出耗时 5 s+ 的效果 let start = new Date().getTime() do { var now = new Date().getTime() } while(5000 >= now - start) // JS 中的最大整数只能到十位 num = parseInt(num) for(var i = 2;i < num; i++) { if (0 === num % i) { break } } if (i < num) { return false } else { return true } }
总结: 项目中使用
Worker
的场景
- 页面中需要进行复杂的运算( Ex: 深度递归, 循环嵌套等 )导致很耗时, 在
UI
主线程中执行会导致页面内容 "卡死", 可以使用Worker
线程与UI
主线程并发执行
-
Ex: 在页面中有两个输入框, 一个按钮, 点击按钮后对两个输入框中的数字进行整数运算( 假设此运算很耗时 ), 最后在一个
div
中显示运算结果<body> <h3>使用 Worker 进行复杂运算</h3> 数字1: <input type="text" name="txtNumberOne" /> 数字2: <input type="text" name="txtNumberTwo" /> <input type="button" class="numComputeBtn" value="开始执行复杂运算" /> <div class="computeResult">0</div> <script type="text/javascript"> let numComputeBtn = document.body.getElementsByClassName('numComputeBtn')[0] numComputeBtn.addEventListener('click', function () { let numOneEl = document.getElementsByName('txtNumberOne')[0] let numTwoEl = document.getElementsByName('txtNumberTwo')[0] let numOneVal = numOneEl.value let numTwoVal = numTwoEl.value if (!numOneVal && numTwoVal) { alert('请输入数据后在计算!') return false } // 创建并执行 Worker 线程 let myWorker = new Worker('numCompute.js') // 发送消息 myWorker.postMessage(`${numOneVal},${numTwoVal}`) // 接受消息 myWorker.addEventListener('message', function (e) { let computeResult = document.body.getElementsByClassName('computeResult')[0] computeResult.innerHTML = e.data }) }) </script> </body>
// numCompute.js // 等待接受 UI 主线程的消息 onmessage = function (e) { let arr = e.data.split(',') let numOne = parseInt(arr[0]) let numTwo = parseInt(arr[1]) // 执行运算 let result = numOne + numTwo // 将处理结果反传给 UI 主线程 postMessage(result) }
3. Web Socket
Socket
: 插座, 套接字, 源自于 90+ 年代 C 语言, 所有的网络通讯底层都是基于套接字编程 套接字用于 "接受数据 & 发送数据"
3.1. HTTP 协议特点
- 属于 "请求-响应" 模型的协议, 必须客户端先发送请求, 服务器才会给出响应; 一个请求, 只能得到一个响应 有些应用场景, 此模型有缺陷: 实时走势, 聊天室 -- 即使客户端不发送请求, 服务器端只要有数据更新也应该立即给客户端
- 解决办法: 长轮询(
Long Polling
) / 心跳请求: 定时器 +XHR
- 该办法并不完美, 心跳过于频繁 -> 服务器压力太大; 不频繁 -> 数据时效性差
3.2. Web Socket 协议
属于 "广播-收听" 模式的协议, 只要客户端连接到服务器就不再断开( 永久连接 ), 双方建立 "全双工通信通道", 一方面不停的给对方发信息, 同时对方也可以发送 / 不发送信息 可以解决 "实时走势, 聊天室" 应用中的问题
3.3. 基于 Web Socket 协议的服务器端程序
java
,PHP
,Node.js
都可以编写
3.4. 基于 Web Socket 协议的客户端程序
java
,PHP
,Node.js
,HTML5
都可以编写
3.5. 使用 Node.js
编写一个 Web Socket 协议的服务器端应用
-
下载第三方
Web Socket
协议模块npm i ws
-
编写
Web Socket
服务端程序// 此 JS 演示 Wen Socket 服务端应用 const ws = require('ws') let server = new ws.Server({ port: 8090 }) server.on('connection', socket => { console.log('WS 服务器接收一个客户端连接......') // 套接字孔1: 从客户端接收消息 socket.on('message', msg => { console.log(`WS 服务器接收到消息: ${msg}`) }) // 套接字孔2: 向客户端发送消息 let counter = 1 let timer = null clearInterval(timer) timer = setInterval(() => { socket.send(`你好, 我是 WS 服务器 - ${counter}`) counter++ }, 1000) // 若连接已断开, 则不再继续发送消息 socket.on('close', () => { console.log('客户端连接已断开') clearInterval(timer) }) })
3.6. 使用 HTML5
编写一个 Web Socket 协议的客户端应用
<body>
<h3>使用 HTML5, 创建 Web Socket 协议的客户端程序</h3>
<button id="btnOne">连接到 WS 服务器</button>
<button id="btnTwo">开始接受 WS 服务器的消息</button>
<button id="btnThree">向服务器发送一条消息</button>
<button id="btnFour">断开 WS 连接</button>
<script type="text/javascript">
// 全局变量: 用于与 WS 服务器通信的套接字对象
let socket = null
// 创建到 WS 服务器连接
let btnOne = document.getElementById('btnOne')
btnOne.addEventListener('click', () => {
socket = new WebSocket('ws://127.0.0.1:8090')
socket.addEventListener('open', function () {
console.log('WS 客户端成功连接到服务器')
})
})
// 接受 WS 服务器的消息
let btnTwo = document.getElementById('btnTwo')
btnTwo.addEventListener('click', () => {
socket.addEventListener('message', e => {
console.log(`客户端接收到一个消息: ${e.data}`)
})
})
// WS 客户端想服务器发送消息
let btnThree = document.getElementById('btnThree')
btnThree.addEventListener('click', () => {
socket.send(`你好, 我是客户端: ${new Date().getTime()}`)
})
// 断开与 WS 服务器的连接
let btnFour = document.getElementById('btnFour')
btnFour.addEventListener('click', () => {
socket.close()
})
</script>
</body>