Hãy nhớ rằng mặc dù JavaScript là một luồng, tất cả I / O của nút và các lệnh gọi tới API gốc đều không đồng bộ (sử dụng cơ chế dành riêng cho nền tảng) hoặc chạy trên một luồng riêng biệt. (Tất cả điều này được xử lý thông qua libuv.)
Vì vậy, khi có dữ liệu có sẵn trên một socket hoặc một hàm API gốc được trả về, chúng ta cần một cách đồng bộ hóa để gọi hàm JavaScript quan tâm đến sự kiện cụ thể vừa xảy ra.
Sẽ không an toàn nếu chỉ gọi hàm JS từ chuỗi nơi sự kiện gốc đã xảy ra vì những lý do tương tự mà bạn gặp phải trong một ứng dụng đa luồng thông thường - điều kiện chủng tộc, quyền truy cập bộ nhớ không nguyên tử, v.v.
Vì vậy, những gì chúng tôi làm là đặt sự kiện vào hàng đợi một cách an toàn theo chuỗi. Trong mã ảo giác đơn giản hóa, một cái gì đó như:
lock (queue) {
queue.push(event);
}
Sau đó, quay lại chuỗi JavaScript chính (nhưng ở phía C của mọi thứ), chúng tôi làm một cái gì đó như:
while (true) {
lock (queue) {
var tickEvents = copy(queue);
queue.empty();
}
for (var i = 0; i < tickEvents.length; i++) {
InvokeJSFunction(tickEvents[i]);
}
}
Các while (true)
(mà không thực sự tồn tại trong mã nguồn của nút, điều này là hoàn toàn minh họa) đại diện cho vòng lặp sự kiện . Bên trong for
gọi hàm JS cho mỗi sự kiện có trong hàng đợi.
Đây là một đánh dấu: sự gọi đồng bộ của không hoặc nhiều hàm gọi lại được liên kết với bất kỳ sự kiện bên ngoài nào. Khi hàng đợi được làm trống và chức năng cuối cùng trả về, dấu tích kết thúc. Chúng tôi quay lại phần đầu (đánh dấu tiếp theo) và kiểm tra các sự kiện đã được thêm vào hàng đợi từ các chuỗi khác trong khi JavaScript của chúng tôi đang chạy .
Điều gì có thể thêm những thứ vào hàng đợi?
process.nextTick
setTimeout
/setInterval
- I / O (nội dung từ
fs
, net
v.v.)
crypto
các chức năng chuyên sâu về bộ xử lý như luồng tiền điện tử, pbkdf2 và PRNG (thực sự là một ví dụ về ...)
- bất kỳ mô-đun gốc nào sử dụng hàng đợi công việc libuv để làm cho các lệnh gọi thư viện C / C ++ đồng bộ trông không đồng bộ