经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » JavaScript » 查看文章
客户端JavaScript的线程池设计详解
来源:jb51  时间:2022/1/24 12:39:40  对本文有异议

1.介绍:

本打算在客户端JavaScript进行机器学习算法计算时应用线程池来优化,就像()演示的神经网络。但是由于各种原因不了了之了。本次遇到了一个新的问题,客户端的MD5运算也是耗时操作,如果同时对多个字符串或文件进行MD5加密就可以使用线程池来优化。

2.准备工作:

到npm官网搜索spark-md5,到其github仓库下载spark-md5.js。该js文件支持AMD,CommonJS和web工作线程的模块系统,我们在实现线程池时,线程工作代码交给web工作线程处理。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3.测试spark-md5是否正常工作:

创建一个网页,再创建一个worker.js用于保存工作线程的代码。以下述代码测试,如果成功输出MD5编码,那么准备工作完成。

客户端网页代码

  1. <script>
  2. let worker = new Worker("worker.js")
  3. worker.postMessage("Danny")
  4. worker.onmessage = function({data}) {
  5. console.log(data)
  6. worker.terminate()
  7. }
  8. </script>

工作线程代码

  1. self.importScripts("spark-md5.js")
  2.  
  3. self.onmessage = function({data}) {
  4. self.postMessage(self.SparkMD5.hash(data))
  5. }

4.线程池设计

1. 目标:本次线程池设计的目标是初始创建n个初始线程,能够满足任意个线程请求,超出n的请求并不丢弃,而是等待到出现空闲线程后再分配之。

2. 基本设计思路:为了基本满足上述目标,至少要有一个线程分配功能,一个线程回收功能。

3. 线程分配功能设计:

  • 线程池满指的是线程池已经没有可用空闲线程
  • 通知对象是一个不可逆状态机,可以用Promise对象来实现
  • 阻塞请求队列存储Promise对象的resolve方法即可
  • 存储线程池中的线程使用数组即可,数组每个元素是一个对象,包括线程和线程状态
  • 返回给用户的可用线程还需要有线程在数组中的下标,在线程释放中会用到

在这里插入图片描述

4. 线程释放功能设计:

  • 线程释放功能需要接收一个参数,为线程的标识,3中设计该标识为数组下标
  • 当线程释放后,查看阻塞请求队列是否为空,如果不为空,说明有被阻塞的线程请求,此时令队首元素出队即可,执行resolve()通知对象的状态变更为Fulfilled

在这里插入图片描述

5. 实现线程池:

  1. class MD5Pool {
  2. // worker用于存储线程
  3. worker = []
  4. // status是线程池状态
  5. status = "Idle"
  6. // 阻塞请求队列
  7. blockRequestQueue = []
  8. // size为用户希望的线程池的容量
  9. constructor(size) {
  10. for(let i = 0; i < size; i ++)
  11. this.worker.push({
  12. worker: new Worker("worker.js"),
  13. status: "Idle"
  14. })
  15. }
  16. // 线程池状态更新函数
  17. statusUpdate() {
  18. let sum = 0
  19. this.worker.forEach(({ status }) => {
  20. if(status === "Busy")
  21. sum ++
  22. })
  23. if(sum === this.worker.length)
  24. this.status = "Busy"
  25. else
  26. this.status = "Idle"
  27. }
  28. // 线程请求方法
  29. assign() {
  30. if(this.status !== "Busy") {
  31. // 此时线程池不满,遍历线程,寻找一个空闲线程
  32. for (let i = 0; i < this.worker.length; i++)
  33. if (this.worker[i].status === "Idle") {
  34. // 该线程空闲,更新状态为忙碌
  35. this.worker[i].status = "Busy"
  36. // 更新线程池状态,如果这是最后一个空闲线程,那么线程池状态变为满
  37. this.statusUpdate()
  38. // 返回给用户该线程,和该线程的标识,标识用数组下标表示
  39. return {
  40. worker: this.worker[i].worker,
  41. index: i
  42. }
  43. }
  44. }
  45. else {
  46. // 此时线程池满
  47. let resolve = null
  48. // 创建一个通知对象
  49. let promise = new Promise(res => {
  50. // 取得通知对象的状态改变方法
  51. resolve = res
  52. })
  53. // 通知对象的状态改变方法加入阻塞请求队列
  54. this.blockRequestQueue.push(resolve)
  55. // 返回给请求者线程池已满信息和通知对象
  56. return {
  57. info: "full",
  58. wait: promise
  59. }
  60. }
  61. }
  62. // 线程释放方法,接收一个参数为线程标识
  63. release(index) {
  64. this.worker[index].status = "Idle"
  65. // 阻塞请求队列中的第一个请求出队,队列中存储的是promise的resolve方法,此时执行,通知请求者已经有可用的线程了
  66. if(this.blockRequestQueue.length)
  67. // 阻塞请求队列队首出列,并执行通知对象的状态改变方法
  68. this.blockRequestQueue.shift()()
  69. // 更新线程池状态,此时一定空闲
  70. this.status = "Idle"
  71. }
  72. }

5.spark-md5对文件进行md5编码

说明:

在3的测试中spark-md5只是对简单字符串进行MD5编码,并非需要大量运算的耗时操作。spark-md5可以对文件进行MD5编码,耗时较多,实现如下。

注意:

spark-md5对文件编码时必须要对文件进行切片后再加密整合,否则不同文件可能会有相同编码。详情见github或npm。

  1. // 在工作线程中引入spark-md5
  2. self.importScripts("spark-md5.js")
  3.  
  4. let fd = new FileReader()
  5. let spark = new self.SparkMD5.ArrayBuffer()
  6.  
  7. // 接收主线程发来的消息,是一个文件
  8. self.onmessage = function(event) {
  9. // 获取文件
  10. let chunk = event.data
  11. // spark-md5要求计算文件的MD5必须切片计算
  12. let chunks = fileSlice(chunk)
  13. // 计算MD5编码
  14. load(chunks)
  15. }
  16.  
  17. // 切片函数
  18. function fileSlice(file) {
  19. let pos = 0
  20. let chunks = []
  21. // 将文件平均切成10分计算MD5
  22. const SLICE_SIZE = Math.ceil(file.size / 10)
  23. while(pos < file.size) {
  24. // slice可以自动处理第二个参数越界
  25. chunks.push(file.slice(pos, pos + SLICE_SIZE))
  26. pos += SLICE_SIZE
  27. }
  28. return chunks
  29. }
  30.  
  31. // MD5计算函数
  32. async function load(chunks) {
  33. for(let i = 0; i < chunks.length; i ++) {
  34. fd.readAsArrayBuffer(chunks[i])
  35. // 在这里希望节约空间,因此复用了FileReader,而不是每次循环新创建一个FileReader。需要等到FileReader完成read后才可以进行下一轮复用,因此用await阻塞。
  36. await new Promise(res => {
  37. fd.onload = function(event) {
  38. spark.append(event.target.result)
  39. if(i === chunks.length - 1) {
  40. self.postMessage(spark.end())
  41. }
  42. res()
  43. }
  44. })
  45. }
  46. }

6.大量文件进行MD5加密并使用线程池优化

下面的测试代码就是对上文所述的拼接

网页代码

  1. <input id="input" type="file" multiple onchange="handleChanged()"/>
  2. <body>
  3. <script>
  4. class MD5Pool {
  5. worker = []
  6. status = "Idle"
  7. blockRequestQueue = []
  8. constructor(size) {
  9. for(let i = 0; i < size; i ++)
  10. this.worker.push({
  11. worker: new Worker("worker.js"),
  12. status: "Idle"
  13. })
  14. }
  15.  
  16. statusUpdate() {
  17. let sum = 0
  18. this.worker.forEach(({ status }) => {
  19. if(status === "Busy")
  20. sum ++
  21. })
  22. if(sum === this.worker.length)
  23. this.status = "Busy"
  24. else
  25. this.status = "Idle"
  26. }
  27.  
  28. assign() {
  29. if(this.status !== "Busy") {
  30. for (let i = 0; i < this.worker.length; i++)
  31. if (this.worker[i].status === "Idle") {
  32. this.worker[i].status = "Busy"
  33. this.statusUpdate()
  34. return {
  35. worker: this.worker[i].worker,
  36. index: i
  37. }
  38. }
  39. }
  40. else {
  41. let resolve = null
  42. let promise = new Promise(res => {
  43. resolve = res
  44. })
  45. this.blockRequestQueue.push(resolve)
  46. return {
  47. info: "full",
  48. wait: promise
  49. }
  50. }
  51. }
  52.  
  53. release(index) {
  54. this.worker[index].status = "Idle"
  55. // 阻塞请求队列中的第一个请求出队,队列中存储的是promise的resolve方法,此时执行,通知请求者已经有可用的线程了
  56. if(this.blockRequestQueue.length)
  57. this.blockRequestQueue.shift()()
  58. this.status = "Idle"
  59. }
  60. }
  61.  
  62. // input点击事件处理函数
  63. function handleChanged() {
  64. let files = event.target.files
  65. // 创建一个大小为2的MD5计算线程池
  66. let pool = new MD5Pool(2)
  67. // 计算切片文件的MD5编码
  68. Array.prototype.forEach.call(files, file => {
  69. getMD5(file, pool)
  70. })
  71. }
  72.  
  73. // 获取文件的MD5编码的函数,第一个参数是文件,第二个参数是MD5线程池
  74. async function getMD5(chunk, pool) {
  75. let thread = pool.assign()
  76. // 如果info为full,那么说明线程池线程已被全部占用,需要等待
  77. if(thread.info === "full") {
  78. // 获取线程通知对象
  79. let wait = thread.wait
  80. // 等到wait兑现时说明已经有可用的线程了
  81. await wait
  82. thread = pool.assign()
  83. let { worker, index } = thread
  84. worker.postMessage(chunk)
  85. worker.onmessage = function (event) {
  86. console.log(event.data)
  87. pool.release(index)
  88. }
  89. } else {
  90. let { worker, index } = thread
  91. worker.postMessage(chunk)
  92. worker.onmessage = function (event) {
  93. console.log(event.data)
  94. pool.release(index)
  95. }
  96. }
  97. }
  98. </script>
  99. </body>

工作线程代码

  1. self.importScripts("spark-md5.js")
  2.  
  3. let fd = new FileReader()
  4. let spark = new self.SparkMD5.ArrayBuffer()
  5.  
  6. self.onmessage = function(event) {
  7. // 获取文件
  8. let chunk = event.data
  9. // spark-md5要求计算文件的MD5必须切片计算
  10. let chunks = fileSlice(chunk)
  11. // 计算MD5编码
  12. load(chunks)
  13. }
  14.  
  15. // 切片函数
  16. function fileSlice(file) {
  17. let pos = 0
  18. let chunks = []
  19. // 将文件平均切成10分计算MD5
  20. const SLICE_SIZE = Math.ceil(file.size / 10)
  21. while(pos < file.size) {
  22. // slice可以自动处理第二个参数越界
  23. chunks.push(file.slice(pos, pos + SLICE_SIZE))
  24. pos += SLICE_SIZE
  25. }
  26. return chunks
  27. }
  28.  
  29. // MD5计算函数
  30. async function load(chunks) {
  31. for(let i = 0; i < chunks.length; i ++) {
  32. fd.readAsArrayBuffer(chunks[i])
  33. // 在这里希望节约空间,因此复用了FileReader,而不是每次循环新创建一个FileReader。需要等到FileReader完成read后才可以进行下一轮复用,因此用await阻塞。
  34. await new Promise(res => {
  35. fd.onload = function(event) {
  36. spark.append(event.target.result)
  37. if(i === chunks.length - 1) {
  38. self.postMessage(spark.end())
  39. }
  40. res()
  41. }
  42. })
  43. }
  44. }

随机选取18个文件进行MD5编码,结果如下

在这里插入图片描述

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注w3xue的更多内容!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号