สำรวจ Node.js Internals
เผยแพร่แล้ว: 2022-03-10นับตั้งแต่การเปิดตัว Node.js โดย Ryan Dahl ที่งาน European JSConf เมื่อวันที่ 8 พฤศจิกายน พ.ศ. 2552 ได้มีการนำไปใช้อย่างกว้างขวางในอุตสาหกรรมเทคโนโลยี บริษัทต่างๆ เช่น Netflix, Uber และ LinkedIn ให้ความน่าเชื่อถือกับคำกล่าวอ้างที่ว่า Node.js สามารถทนต่อการรับส่งข้อมูลและการทำงานพร้อมกันจำนวนมาก
ด้วยความรู้พื้นฐาน นักพัฒนาระดับเริ่มต้นและระดับกลางของ Node.js ต้องต่อสู้กับหลายสิ่งหลายอย่าง: “มันก็แค่รันไทม์!” “มีเหตุการณ์วนซ้ำ!” “Node.js เป็นแบบเธรดเดียวเหมือน JavaScript!”
แม้ว่าการกล่าวอ้างบางข้อจะเป็นความจริง เราจะเจาะลึกลงไปในรันไทม์ Node.js ทำความเข้าใจวิธีรัน JavaScript ดูว่าจริง ๆ แล้วเป็นเธรดเดียวหรือไม่ และสุดท้าย ทำความเข้าใจการเชื่อมต่อระหว่างการอ้างอิงหลัก V8 และ libuv .
ข้อกำหนดเบื้องต้น
- ความรู้พื้นฐานของจาวาสคริปต์
- ความคุ้นเคยกับ Node.js semantics (
require
,fs
)
Node.js คืออะไร?
อาจเป็นการเย้ายวนที่จะสมมติสิ่งที่หลายคนเชื่อเกี่ยวกับ Node.js คำจำกัดความทั่วไปที่สุดของมันคือว่าเป็น รันไทม์สำหรับภาษา JavaScript ในการพิจารณาสิ่งนี้ เราควรเข้าใจว่าอะไรนำไปสู่ข้อสรุปนี้
Node.js มักถูกอธิบายว่าเป็นการผสมผสานระหว่าง C++ และ JavaScript ส่วน C++ ประกอบด้วยการเชื่อมโยงที่เรียกใช้โค้ดระดับต่ำที่ทำให้สามารถเข้าถึงฮาร์ดแวร์ที่เชื่อมต่อกับคอมพิวเตอร์ได้ ส่วน JavaScript ใช้ JavaScript เป็นซอร์สโค้ดและรันในล่ามภาษาที่ได้รับความนิยม ซึ่งมีชื่อว่าเอ็นจิ้น V8
ด้วยความเข้าใจนี้ เราสามารถอธิบาย Node.js ว่าเป็นเครื่องมือพิเศษที่รวม JavaScript และ C++ เพื่อเรียกใช้โปรแกรมนอกสภาพแวดล้อมของเบราว์เซอร์
แต่เราสามารถเรียกมันว่ารันไทม์ได้จริงหรือ? เพื่อตรวจสอบว่า ให้กำหนดว่ารันไทม์คืออะไร
รันไทม์คืออะไร? https://t.co/eaF4CoWecX
— Christian Nwamba (@codebeast) วันที่ 5 มีนาคม 2020
หนึ่งในคำตอบของเขาใน StackOverflow นั้น DJNA ได้กำหนดสภาพแวดล้อมรันไทม์เป็น “ทุกอย่างที่คุณต้องการเพื่อรันโปรแกรม แต่ไม่มีเครื่องมือใดที่จะเปลี่ยนแปลงได้” ตามคำจำกัดความนี้ เราสามารถพูดได้อย่างมั่นใจว่าทุกอย่างที่เกิดขึ้นในขณะที่เรารันโค้ดของเรา (ในภาษาใดๆ ก็ตาม) กำลังทำงานในสภาพแวดล้อมรันไทม์
ภาษาอื่นมีสภาพแวดล้อมรันไทม์ของตัวเอง สำหรับ Java มันคือ Java Runtime Environment (JRE) สำหรับ .NET จะเป็น Common Language Runtime (CLR) สำหรับ Erlang ก็คือ BEAM
อย่างไรก็ตาม รันไทม์เหล่านี้บางส่วนมีภาษาอื่นที่ขึ้นอยู่กับพวกเขา ตัวอย่างเช่น Java มี Kotlin ซึ่งเป็นภาษาการเขียนโปรแกรมที่คอมไพล์เป็นโค้ดที่ JRE สามารถเข้าใจได้ เออร์ลังมีน้ำยาอีลิกเซอร์ และเรารู้ว่ามีหลากหลายรูปแบบสำหรับการพัฒนา .NET ซึ่งทั้งหมดทำงานใน CLR หรือที่เรียกว่า .NET Framework
ตอนนี้เราเข้าใจแล้วว่ารันไทม์เป็นสภาพแวดล้อมที่จัดเตรียมไว้สำหรับโปรแกรมเพื่อให้สามารถดำเนินการได้สำเร็จ และเรารู้ว่า V8 และโฮสต์ของไลบรารี C++ ทำให้แอปพลิเคชัน Node.js สามารถดำเนินการได้ Node.js เองเป็นรันไทม์จริงที่รวมทุกอย่างเข้าด้วยกันเพื่อทำให้ไลบรารีเหล่านี้เป็นเอนทิตี และเข้าใจเพียงภาษาเดียว — JavaScript — ไม่ว่า Node.js จะสร้างขึ้นด้วยอะไรก็ตาม
โครงสร้างภายในของ Node.js
เมื่อเราพยายามเรียกใช้โปรแกรม Node.js (เช่น index.js
) จากบรรทัดคำสั่งของเราโดยใช้ node index.js
เรากำลังเรียกใช้ Node.js รันไทม์ รันไทม์นี้ตามที่กล่าวไว้ประกอบด้วยการพึ่งพาอิสระสองรายการคือ V8 และ libuv
V8 เป็นโครงการที่สร้างและดูแลโดย Google ใช้ซอร์สโค้ด JavaScript และรันนอกสภาพแวดล้อมเบราว์เซอร์ เมื่อเรารันโปรแกรมผ่านคำสั่ง node
ซอร์สโค้ดจะถูกส่งผ่านรันไทม์ Node.js ไปยัง V8 เพื่อดำเนินการ
ไลบรารี libuv มีโค้ด C++ ที่ช่วยให้สามารถเข้าถึงระบบปฏิบัติการในระดับต่ำได้ ฟังก์ชันการทำงาน เช่น เครือข่าย การเขียนไปยังระบบไฟล์ และการทำงานพร้อมกันจะไม่ถูกส่งโดยค่าเริ่มต้นใน V8 ซึ่งเป็นส่วนหนึ่งของ Node.js ที่เรียกใช้โค้ด JavaScript ของเรา ด้วยชุดของไลบรารี libuv จัดเตรียมยูทิลิตีเหล่านี้และอื่น ๆ ในสภาพแวดล้อม Node.js
Node.js เป็นกาวที่ยึดไลบรารีทั้งสองไว้ด้วยกัน จึงกลายเป็นโซลูชันที่ไม่เหมือนใคร ตลอดการใช้งานสคริปต์ Node.js จะเข้าใจว่าโครงการใดต้องผ่านการควบคุมและเมื่อใด
API ที่น่าสนใจสำหรับโปรแกรมฝั่งเซิร์ฟเวอร์
หากเราศึกษาประวัติจาวาสคริปต์เพียงเล็กน้อย เราจะรู้ว่าจาวาสคริปต์มีไว้เพื่อเพิ่มฟังก์ชันการทำงานและการโต้ตอบบางอย่างให้กับหน้าในเบราว์เซอร์ และในเบราว์เซอร์ เราจะโต้ตอบกับองค์ประกอบของรูปแบบวัตถุเอกสาร (DOM) ที่ประกอบเป็นหน้า สำหรับสิ่งนี้ มีชุดของ API อยู่ ซึ่งเรียกรวมกันว่า DOM API
DOM มีอยู่ในเบราว์เซอร์เท่านั้น คือสิ่งที่ถูกแยกวิเคราะห์เพื่อแสดงหน้าเว็บ และโดยทั่วไปแล้วจะเขียนในภาษามาร์กอัปที่เรียกว่า HTML นอกจากนี้ เบราว์เซอร์ยังมีอยู่ในหน้าต่าง ดังนั้นวัตถุ window
ซึ่งทำหน้าที่เป็นรูทสำหรับออบเจ็กต์ทั้งหมดบนหน้าในบริบท JavaScript สภาพแวดล้อมนี้เรียกว่าสภาพแวดล้อมของเบราว์เซอร์และเป็นสภาพแวดล้อมรันไทม์สำหรับ JavaScript
ในสภาพแวดล้อม Node.js เราไม่มีอะไรเหมือนหน้าหรือเบราว์เซอร์ ซึ่งจะทำให้ความรู้ของเราเกี่ยวกับวัตถุหน้าต่างส่วนกลางเป็นโมฆะ สิ่งที่เรามีคือชุดของ API ที่โต้ตอบกับระบบปฏิบัติการเพื่อให้ฟังก์ชันเพิ่มเติมแก่โปรแกรม JavaScript API เหล่านี้สำหรับ Node.js ( fs
, path
, buffer
, events
, HTTP
และอื่น ๆ ) ตามที่เรามี มีอยู่สำหรับ Node.js เท่านั้น และ Node.js จัดเตรียมไว้ให้ (เป็นรันไทม์) เพื่อให้เรา สามารถเรียกใช้โปรแกรมที่เขียนขึ้นสำหรับ Node.js
การทดลอง: fs.writeFile
สร้างไฟล์ใหม่อย่างไร
หาก V8 ถูกสร้างขึ้นเพื่อเรียกใช้ JavaScript นอกเบราว์เซอร์ และหากสภาพแวดล้อม Node.js ไม่มีบริบทหรือสภาพแวดล้อมเดียวกันกับเบราว์เซอร์ เราจะทำบางสิ่งเช่นเข้าถึงระบบไฟล์หรือสร้างเซิร์ฟเวอร์ HTTP ได้อย่างไร
ตัวอย่างเช่น ลองใช้แอปพลิเคชัน Node.js ธรรมดาที่เขียนไฟล์ไปยังระบบไฟล์ในไดเร็กทอรีปัจจุบัน:
const fs = require("fs") fs.writeFile("./test.txt", "text");
ดังที่แสดง เรากำลังพยายามเขียนไฟล์ใหม่ไปยังระบบไฟล์ คุณลักษณะนี้ไม่พร้อมใช้งานในภาษา JavaScript มีให้ใช้งานในสภาพแวดล้อม Node.js เท่านั้น สิ่งนี้จะถูกดำเนินการอย่างไร?
เพื่อให้เข้าใจสิ่งนี้ เรามาสำรวจฐานโค้ด Node.js กัน
มุ่งหน้าไปยังที่เก็บ GitHub สำหรับ Node.js เราจะเห็นสองโฟลเดอร์หลัก src
และ lib
โฟลเดอร์ lib
มีโค้ด JavaScript ที่ให้ชุดโมดูลที่ดีซึ่งรวมอยู่ตามค่าเริ่มต้นในการติดตั้ง Node.js ทุกครั้ง โฟลเดอร์ src
มีไลบรารี C++ สำหรับ libuv
หากเราดูในโฟลเดอร์ lib
และเข้าไปที่ไฟล์ fs.js
เราจะเห็นว่าเต็มไปด้วยโค้ด JavaScript ที่น่าประทับใจ ในบรรทัดที่ 1880 เราจะสังเกตเห็นคำสั่งการ exports
คำสั่งนี้ส่งออกทุกสิ่งที่เราสามารถเข้าถึงได้โดยการนำเข้าโมดูล fs
และเราจะเห็นว่ามันส่งออกฟังก์ชันที่ชื่อ writeFile
การค้นหา function writeFile(
(โดยที่ฟังก์ชันถูกกำหนดไว้) นำเราไปสู่บรรทัดที่ 1303 ซึ่งเราจะเห็นว่าฟังก์ชันถูกกำหนดด้วยพารามิเตอร์สี่ตัว:
function writeFile(path, data, options, callback) { callback = maybeCallback(callback || options); options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' }); const flag = options.flag || 'w'; if (!isArrayBufferView(data)) { validateStringAfterArrayBufferView(data, 'data'); data = Buffer.from(data, options.encoding || 'utf8'); } if (isFd(path)) { const isUserFd = true; writeAll(path, isUserFd, data, 0, data.byteLength, callback); return; } fs.open(path, flag, options.mode, (openErr, fd) => { if (openErr) { callback(openErr); } else { const isUserFd = false; writeAll(fd, isUserFd, data, 0, data.byteLength, callback); } }); }
ในบรรทัดที่ 1315 และ 1324 เราเห็นว่ามีการเรียกฟังก์ชันเดียว writeAll
หลังจากการตรวจสอบความถูกต้องบางอย่าง เราพบฟังก์ชันนี้ในบรรทัดที่ 1278 ในไฟล์ fs.js
เดียวกัน
function writeAll(fd, isUserFd, buffer, offset, length, callback) { // write(fd, buffer, offset, length, position, callback) fs.write(fd, buffer, offset, length, null, (writeErr, written) => { if (writeErr) { if (isUserFd) { callback(writeErr); } else { fs.close(fd, function close() { callback(writeErr); }); } } else if (written === length) { if (isUserFd) { callback(null); } else { fs.close(fd, callback); } } else { offset += written; length -= written; writeAll(fd, isUserFd, buffer, offset, length, callback); } }); }
นอกจากนี้ยังเป็นที่น่าสนใจที่จะทราบว่าโมดูลนี้พยายามเรียกตัวเอง เราเห็นสิ่งนี้ในบรรทัด 1280 ซึ่งเรียกมันว่า fs.write
มองหาฟังก์ชัน write
เราจะค้นพบข้อมูลเล็กน้อย
ฟังก์ชัน write
เริ่มต้นในบรรทัด 571 และทำงานประมาณ 42 บรรทัด เราเห็นรูปแบบที่เกิดซ้ำในฟังก์ชันนี้: วิธีที่เรียกใช้ฟังก์ชันบนโมดูลการ binding
ดังที่เห็นในบรรทัดที่ 594 และ 612 ฟังก์ชันบนโมดูลการ binding
ไม่เพียงถูกเรียกในฟังก์ชันนี้เท่านั้น แต่ในฟังก์ชันแทบทุกฟังก์ชันที่ส่งออก ในไฟล์ fs.js
บางสิ่งบางอย่างจะต้องพิเศษมากเกี่ยวกับเรื่องนี้
ตัวแปรการ binding
ถูกประกาศในบรรทัดที่ 58 ที่ด้านบนสุดของไฟล์ และการคลิกที่การเรียกใช้ฟังก์ชันนั้นจะแสดงข้อมูลบางอย่างด้วยความช่วยเหลือของ GitHub
ฟังก์ชัน internalBinding
นี้พบได้ในโมดูลที่ชื่อ loaders หน้าที่หลักของโมดูลตัวโหลดคือการโหลดไลบรารี libuv ทั้งหมดและเชื่อมต่อผ่านโปรเจ็กต์ V8 ด้วย Node.js วิธีนี้ค่อนข้างวิเศษ แต่หากต้องการเรียนรู้เพิ่มเติม เราสามารถพิจารณาอย่างใกล้ชิดที่ฟังก์ชัน writeBuffer
ที่เรียกใช้โดยโมดูล fs
เราควรดูว่าสิ่งนี้เชื่อมต่อกับ libuv ที่ไหน และ V8 เข้ามาที่ใด ที่ด้านบนสุดของโมดูล loaders เอกสารที่ดีบางส่วนระบุว่า:
// This file is compiled and run by node.cc before bootstrap/node.js // was called, therefore the loaders are bootstraped before we start to // actually bootstrap Node.js. It creates the following objects: // // C++ binding loaders: // - process.binding(): the legacy C++ binding loader, accessible from user land // because it is an object attached to the global process object. // These C++ bindings are created using NODE_BUILTIN_MODULE_CONTEXT_AWARE() // and have their nm_flags set to NM_F_BUILTIN. We do not make any guarantees // about the stability of these bindings, but still have to take care of // compatibility issues caused by them from time to time. // - process._linkedBinding(): intended to be used by embedders to add // additional C++ bindings in their applications. These C++ bindings // can be created using NODE_MODULE_CONTEXT_AWARE_CPP() with the flag // NM_F_LINKED. // - internalBinding(): the private internal C++ binding loader, inaccessible // from user land unless through `require('internal/test/binding')`. // These C++ bindings are created using NODE_MODULE_CONTEXT_AWARE_INTERNAL() // and have their nm_flags set to NM_F_INTERNAL. // // Internal JavaScript module loader: // - NativeModule: a minimal module system used to load the JavaScript core // modules found in lib/**/*.js and deps/**/*.js. All core modules are // compiled into the node binary via node_javascript.cc generated by js2c.py, // so they can be loaded faster without the cost of I/O. This class makes the // lib/internal/*, deps/internal/* modules and internalBinding() available by // default to core modules, and lets the core modules require itself via // require('internal/bootstrap/loaders') even when this file is not written in // CommonJS style.
สิ่งที่เราเรียนรู้ที่นี่คือสำหรับทุกโมดูลที่เรียกจากอ็อบเจกต์การ binding
ในส่วน JavaScript ของโปรเจ็กต์ Node.js จะมีความเท่าเทียมกันในส่วน C++ ในโฟลเดอร์ src
จากทัวร์ชมของเรา เราจะเห็นว่าโมดูลที่ทำสิ่งนี้อยู่ node_file.cc
fs
ทุกฟังก์ชันที่เข้าถึงได้ผ่านโมดูลถูกกำหนดไว้ในไฟล์ ตัวอย่างเช่น เรามี writeBuffer
ในบรรทัดที่ 2258 คำจำกัดความที่แท้จริงของเมธอดนั้นในไฟล์ C++ อยู่ในบรรทัดที่ 1785 นอกจากนี้ การเรียกไปยังส่วนของ libuv ที่เขียนไฟล์จริงสามารถพบได้ในบรรทัดที่ 1809 และ พ.ศ. 2358 โดยที่ฟังก์ชัน uv_fs_write
ถูกเรียกแบบอะซิงโครนัส
เราได้อะไรจากความเข้าใจนี้?
เช่นเดียวกับรันไทม์ภาษาอื่น ๆ ที่ตีความ รันไทม์ของ Node.js สามารถถูกแฮ็กได้ ด้วยความเข้าใจที่มากขึ้น เราสามารถทำสิ่งต่าง ๆ ที่เป็นไปไม่ได้ด้วยการกระจายแบบมาตรฐานเพียงแค่มองผ่านแหล่งที่มา เราสามารถเพิ่มไลบรารี่เพื่อเปลี่ยนแปลงวิธีการเรียกใช้ฟังก์ชันบางอย่างได้ แต่เหนือสิ่งอื่นใด ความเข้าใจนี้เป็นรากฐานสำหรับการสำรวจเพิ่มเติม
Node.js เป็นแบบเธรดเดียวหรือไม่
Node.js ทำงานบน libuv และ V8 สามารถเข้าถึงฟังก์ชันการทำงานเพิ่มเติมบางอย่างที่เอ็นจิ้น JavaScript ทั่วไปที่ทำงานในเบราว์เซอร์ไม่มี
JavaScript ใดๆ ที่ทำงานในเบราว์เซอร์จะทำงานในเธรดเดียว เธรดในการทำงานของโปรแกรมนั้นเหมือนกับกล่องดำที่อยู่ด้านบนของ CPU ที่โปรแกรมกำลังดำเนินการอยู่ ในบริบทของ Node.js โค้ดบางตัวสามารถดำเนินการในเธรดได้มากเท่าที่เครื่องของเราสามารถดำเนินการได้
ในการตรวจสอบการอ้างสิทธิ์นี้ เรามาสำรวจข้อมูลโค้ดอย่างง่ายกัน
const fs = require("fs"); // A little benchmarking const startTime = Date.now() fs.writeFile("./test.txt", "test", (err) => { If (error) { console.log(err) } console.log("1 Done: ", Date.now() — startTime) });
ในตัวอย่างด้านบน เรากำลังพยายามสร้างไฟล์ใหม่บนดิสก์ในไดเร็กทอรีปัจจุบัน เพื่อดูว่าอาจใช้เวลานานเท่าใด เราได้เพิ่มเกณฑ์มาตรฐานเล็กน้อยเพื่อตรวจสอบเวลาเริ่มต้นของสคริปต์ ซึ่งทำให้เรามีระยะเวลาเป็นมิลลิวินาทีของสคริปต์ที่กำลังสร้างไฟล์
หากเรารันโค้ดด้านบน เราจะได้ผลลัพธ์ดังนี้:
$ node ./test.js -> 1 Done: 0.003s
สิ่งนี้น่าประทับใจมาก: เพียง 0.003 วินาที
แต่มาทำอะไรที่น่าสนใจจริงๆ ขั้นแรก ให้ทำซ้ำโค้ดที่สร้างไฟล์ใหม่ และอัปเดตตัวเลขในคำสั่งบันทึกเพื่อแสดงตำแหน่ง:
const fs = require("fs"); // A little benchmarking const startTime = Date.now() fs.writeFile("./test1.txt", "test", function (err) { if (err) { console.log(err) } console.log("1 Done: %ss", (Date.now() — startTime) / 1000) }); fs.writeFile("./test2.txt", "test", function (err) { if (err) { console.log(err) } console.log("2 Done: %ss", (Date.now() — startTime) / 1000) }); fs.writeFile("./test3.txt", "test", function (err) { if (err) { console.log(err) } console.log("3 Done: %ss", (Date.now() — startTime) / 1000) }); fs.writeFile("./test4.txt", "test", function (err) { if (err) { console.log(err) } console.log("4 Done: %ss", (Date.now() — startTime) / 1000) });
หากเราพยายามเรียกใช้โค้ดนี้ เราจะได้รับบางสิ่งที่โดนใจเรา นี่คือผลลัพธ์ของฉัน:
อันดับแรก เราจะสังเกตได้ว่าผลลัพธ์ไม่สอดคล้องกัน ประการที่สอง เราเห็นว่าเวลาเพิ่มขึ้น เกิดอะไรขึ้น?
งานระดับต่ำรับมอบหมาย
Node.js เป็นแบบเธรดเดียวอย่างที่เราทราบตอนนี้ บางส่วนของ Node.js เขียนด้วย JavaScript และส่วนอื่นๆ ใน C++ Node.js ใช้แนวคิดเดียวกันกับการวนรอบเหตุการณ์และ call stack ที่เราคุ้นเคยจากสภาพแวดล้อมของเบราว์เซอร์ ซึ่งหมายความว่าส่วน JavaScript ของ Node.js เป็นแบบเธรดเดียว แต่งานระดับต่ำที่ต้องพูดกับระบบปฏิบัติการนั้นไม่ใช่งานแบบเธรดเดียว
เมื่อ Node.js รู้จักการโทรว่ามีไว้สำหรับ libuv จะมีการมอบหมายงานนี้ให้กับ libuv ในการดำเนินการ libuv ต้องการเธรดสำหรับไลบรารีบางตัว ดังนั้นการใช้เธรดพูลในการรันโปรแกรม Node.js เมื่อจำเป็น
ตามค่าเริ่มต้น เธรดพูล Node.js ที่จัดเตรียมโดย libuv จะมีสี่เธรดอยู่ในนั้น เราสามารถเพิ่มหรือลดกลุ่มเธรดนี้ได้โดยเรียก process.env.UV_THREADPOOL_SIZE
ที่ด้านบนสุดของสคริปต์ของเรา
// script.js process.env.UV_THREADPOOL_SIZE = 6; // … // …
จะเกิดอะไรขึ้นกับโปรแกรมสร้างไฟล์ของเรา
ดูเหมือนว่าเมื่อเราเรียกใช้โค้ดเพื่อสร้างไฟล์ของเรา Node.js จะเข้าชมส่วน libuv ของโค้ด ซึ่งอุทิศเธรดสำหรับงานนี้ ส่วนนี้ใน libuv รับข้อมูลทางสถิติเกี่ยวกับดิสก์ก่อนที่จะทำงานกับไฟล์
การตรวจสอบทางสถิตินี้อาจใช้เวลาสักครู่จึงจะเสร็จสมบูรณ์ ดังนั้น เธรดจะถูกปล่อยสำหรับงานอื่น ๆ จนกว่าการตรวจสอบทางสถิติจะเสร็จสิ้น เมื่อการตรวจสอบเสร็จสิ้น ส่วน libuv จะใช้เธรดที่มีอยู่หรือรอจนกว่าเธรดจะพร้อมใช้งาน
เรามีเพียงสี่สายและสี่เธรด ดังนั้นจึงมีเธรดที่เพียงพอ คำถามเดียวคือแต่ละเธรดจะประมวลผลงานได้เร็วเพียงใด เราจะสังเกตเห็นว่าโค้ดแรกที่สร้างลงในกลุ่มของเธรดจะส่งคืนผลลัพธ์ก่อน และจะบล็อกเธรดอื่นๆ ทั้งหมดในขณะที่รันโค้ด
บทสรุป
ตอนนี้เราเข้าใจแล้วว่า Node.js คืออะไร เรารู้ว่ามันเป็นรันไทม์ เราได้กำหนดว่ารันไทม์คืออะไร และเราได้เจาะลึกถึงสิ่งที่ประกอบขึ้นเป็นรันไทม์ของ Node.js
เรามาไกลมากแล้ว และจากการทัวร์ชมที่เก็บ Node.js บน GitHub เพียงเล็กน้อย เราสามารถสำรวจ API ใด ๆ ที่เราอาจสนใจ ตามกระบวนการเดียวกับที่เราดำเนินการที่นี่ Node.js เป็นโอเพ่นซอร์ส ดังนั้นแน่นอนว่าเราสามารถดำดิ่งสู่ซอร์สได้ใช่ไหม
แม้ว่าเราจะได้สัมผัสกับระดับต่ำของสิ่งที่เกิดขึ้นในรันไทม์ Node.js แล้ว เราต้องไม่คิดว่าเรารู้ทั้งหมด แหล่งข้อมูลด้านล่างชี้ไปที่ข้อมูลบางส่วนที่เราสามารถสร้างความรู้ของเราได้:
- ข้อมูลเบื้องต้นเกี่ยวกับ Node.js
ในฐานะที่เป็นเว็บไซต์อย่างเป็นทางการ Node.dev อธิบายว่า Node.js คืออะไร รวมถึงตัวจัดการแพ็คเกจ และแสดงรายการเฟรมเวิร์กเว็บที่สร้างขึ้นเพิ่มเติม - “JavaScript & Node.js” หนังสือ Node Beginner
หนังสือเล่มนี้โดย Manuel Kiessling อธิบาย Node.js ได้อย่างยอดเยี่ยม หลังจากเตือนว่า JavaScript ในเบราว์เซอร์ไม่เหมือนกับใน Node.js แม้ว่าทั้งคู่จะเขียนด้วยภาษาเดียวกันก็ตาม - เริ่มต้น Node.js
หนังสือสำหรับผู้เริ่มต้นเล่มนี้มีมากกว่าคำอธิบายเกี่ยวกับรันไทม์ มันสอนเกี่ยวกับแพ็คเกจและสตรีมและการสร้างเว็บเซิร์ฟเวอร์ด้วยกรอบงาน Express - LibUV
นี่คือเอกสารอย่างเป็นทางการของรหัส C++ ที่รองรับของรันไทม์ Node.js - V8
นี่คือเอกสารอย่างเป็นทางการของเอ็นจิ้น JavaScript ที่ทำให้สามารถเขียน Node.js ด้วย JavaScript ได้