يدعم HTML5 واجهات برمجة التطبيقات مثل عامل الويب ، مما يسمح لصفحات الويب بتنفيذ التعليمات البرمجية متعددة الخيوط في موقف آمن. ومع ذلك ، يخضع عامل الويب في الواقع للعديد من القيود لأنه لا يمكنه مشاركة بيانات الذاكرة حقًا ولا يمكنه استخدام الرسائل لإبلاغ الحالة إلا ، لذلك لا يمكن أن يطلق عليها "متعددة الخيوط" بالمعنى الحقيقي.
واجهة عامل الويب غير مريح للغاية للاستخدام. إنه يأتي بشكل أساسي مع صندوق رمل ، والذي يدير ملف JS مستقل في صندوق الرمل ، ويتواصل مع الخيط الرئيسي من خلال postmessage و onMessge:
نسخة الكود كما يلي:
var Worker = New Worker ("my.js") ؛
var bundle = {message: 'hello world' ، id: 1} ؛
عامل. postmessage (حزمة) ؛ // يمكن أن يمرر postmessage كائن قابل للتسلسل في الماضي
عامل.
console.log (evt.data) ؛ // قارن الكائنات التي تم تمريرها مرة أخرى في العامل مع كائنات في الخيط الرئيسي
console.log (حزمة) ؛ // {Message: 'Hello World' ، id: 1}
}
نسخة الكود كما يلي:
// في my.js
onMessage = function (evt) {
var data = evt.data ؛
data.id ++ ؛
postmessage (البيانات) ؛ // {Message: 'Hello World' ، id: 2}
}
يمكن العثور على النتيجة أن معرف البيانات التي تم الحصول عليها في الخيط قد زاد ، ولكن بعد مروره ، لم يتم تغيير المعرف في حزمة الخيط الرئيسي. لذلك ، يتم نسخ الكائن الذي تم تمريره في الخيط بالفعل. وبهذه الطريقة ، لا يشارك مؤشر الترابط البيانات ، وتجنب القراءة والكتابات ، لذلك فهو آمن. تكلفة ضمان سلامة مؤشرات الترابط هي الحد من القدرة على تشغيل كائنات الخيط الرئيسية في مؤشر الترابط.
مثل هذه الآلية المحدودة متعددة الخيوط غير مريحة للاستخدام. بالطبع ، نأمل أن يتمكن العامل من دعم القدرة على جعل الكود يعمل على تشغيل متعدد الخيوط في نفس الوقت. على سبيل المثال ، رمز الدعم الذي يشبه ما يلي:
نسخة الكود كما يلي:
var worker = new threadworker (bundle /*shared obj* /) ؛
Worker.run (وظيفة (حزمة) {
// do sth في موضوع العمال ...
this.runonuithread (وظيفة (حزمة /*مشتركة OBJ* /) {
// هل sth في موضوع واجهة المستخدم الرئيسية ...
}) ؛
// ...
}) ؛
في هذا الرمز ، بعد بدء تشغيل عامل ، يمكننا السماح لأي رمز يعمل في العامل ، وعندما نحتاج إلى تشغيل سلسلة واجهة المستخدم (مثل قراءة وكتابة DOM) ، يمكننا العودة إلى مؤشر الترابط الرئيسي للتنفيذ من خلال this.runonuithread.
إذن كيف تنفذ هذه الآلية؟ انظر إلى الكود التالي:
نسخة الكود كما يلي:
عامل عامل (shareObj) {
this._worker = عامل جديد ("thread.js") ؛
this._completes = {} ؛
this._task_id = 0 ؛
this.sharedObj = sharedobj ؛
var self = this ؛
this._worker.onmessage = function (evt) {
var ret = evt.data ؛
if (ret .__ ui_task __) {
// قم بتشغيل مهمة واجهة المستخدم
var fn = (دالة جديدة ("return"+ret .__ ui_task __)) () ؛
fn (ret.sharedobj) ؛
}آخر{
self.sharedobj = ret.sharedobj ؛
self._completes [ret.taskid] (ret) ؛
}
}
}
WorkerThread.prototype.run = وظيفة (المهمة ، كاملة) {
var _task = {__thread_task __: task.toString () ، sharedobj: this.sharedobj ، taskid: this._task_id} ؛
this._completes [this._task_id ++] = complete ؛
this._worker.postmessage (_task) ؛
}
يحدد الكود أعلاه كائن Threadworker. يقوم هذا الكائن بإنشاء عامل ويب يقوم بتشغيل Thread.js ، ويحفظ الكائن المشترك المشترك ، ويعالج الرسائل المرسلة بواسطة Thread.js.
إذا تم إرسال رسالة ui_task مرة أخرى في Thread.js ، فتشغيل الوظيفة التي تم تمريرها بواسطة هذه الرسالة. خلاف ذلك ، قم بتنفيذ رد الاتصال الكامل للتشغيل ، دعنا نرى كيف يتم كتابة Thread.js:
نسخة الكود كما يلي:
onMessage = function (evt) {
var data = evt.data ؛
if (data && data .__ thread_task __) {
var task = data .__ thread_task__ ؛
يحاول{
var fn = (وظيفة جديدة ("إرجاع"+مهمة))) () ؛
var ctx = {
ThreadSignal: صحيح ،
النوم: وظيفة (فاصل) {
CTX.ThreadSignal = false ؛
setTimeout (_run ، الفاصل الزمني) ؛
} ،
RunonuithRead: Function (Task) {
postMessage ({__ ui_task __: task.toString () ، shareObj: data.sharedobj}) ؛
}
}
وظيفة _run () {
CTX.ThreadSignal = صحيح ؛
var ret = fn.call (ctx ، data.sharedobj) ؛
postMessage ({error: null ، returnvalue: ret ، __thread_task __: task ، sharedobj: data.sharedobj ، taskid: data.taskid}) ؛
}
_run (0) ؛
} catch (ex) {
postmessage ({error: ex.toString () ، returnValue: null ، sharedObj: data.sharedobj}) ؛
}
}
}
يمكن ملاحظة أن Thread.js يتلقى رسائل من مؤشرات ترابط واجهة المستخدم ، وأهمها thread_task ، وهي "المهمة" التي يتم تمريرها بواسطة مؤشرات ترابط واجهة المستخدم التي تحتاج إلى تنفيذها بواسطة مؤشر ترابط العامل. نظرًا لأن الوظيفة غير قابلة للتسلسل ، يتم تمرير سلسلة. يقوم مؤشر ترابط العامل بتوزيع السلسلة في دالة لتنفيذ المهام المقدمة من مؤشر الترابط الرئيسي (لاحظ أن الكائن المشترك مشترك يتم تمريره في المهمة). بعد الانتهاء من التنفيذ ، سيتم تمرير نتيجة الإرجاع إلى مؤشر ترابط واجهة المستخدم من خلال الرسالة. دعنا نلقي نظرة فاحصة على الكائن المشترك المشترك بالإضافة إلى قيمة إرجاع قيمة الإرجاع. عند المرور ، نظرًا لأن مؤشر ترابط العامل وخيط واجهة المستخدم لا يشاركان الكائن ، فإننا نزامن بشكل مصطنع الكائنات على كلا الجانبين من خلال المهمة (هل هذا الموضوع آمن؟ لماذا؟)
يمكنك أن ترى أن العملية برمتها ليست معقدة. بعد هذا التنفيذ ، يمكن أن يحصل هذا العامل على الاستخدامين التاليين:
نسخة الكود كما يلي:
var t1 = new WorkerThRead ({i: 100} /*shared obj* /) ؛
setInterval (function () {
t1.run (وظيفة (مشترك) {
return sharedobj.i ++ ؛
} ،
وظيفة (ص) {
console.log ("T1>" + R.ReturnValue + ":" + R.Error) ؛
}
) ؛
} ، 500) ؛
var t2 = عامل جديد ({i: 50}) ؛
t2.run (وظيفة (sharedobj) {
بينما (this.threadsignal) {
sharedobj.i ++ ؛
this.runonuithread (وظيفة (shareObj) {
W ("body ul"). appendChild ("<li>"+sharedobj.i+"</li>") ؛
}) ؛
this.sleep (500) ؛
}
return sharedobj.i ؛
} ، وظيفة (ص) {
console.log ("T2>" + R.ReturnValue + ":" + R.Error) ؛
}) ؛
يمنح هذا الاستخدام الكود بنية جيدة ، والمرونة والقابلية للصيانة في كل من النموذج والدلالة الدلالية.
حسنًا ، هذا كل شيء للمناقشة حول استخدام عامل الويب. يمكن للطلاب المهتمين إلقاء نظرة على هذا المشروع: https://github.com/akira-cn/workerthread.js (نظرًا لأن العامل يحتاج إلى استخدام اختبار الخادم ، فأنا وضعت خصيصًا httpd.js في المشروع ، وهي خدمة HTTP بسيطة للغاية ، ويمكنك تشغيلها مع Node مباشرة).