Hiểu vòng lặp sự kiện


134

Tôi đang suy nghĩ về nó và đây là những gì tôi nghĩ ra:

Hãy nói rằng chúng ta có một mã như thế này:

console.clear();
console.log("a");
setTimeout(function(){console.log("b");},1000);
console.log("c");
setTimeout(function(){console.log("d");},0);

Một yêu cầu xuất hiện và công cụ JS bắt đầu thực thi mã ở trên từng bước. Hai cuộc gọi đầu tiên là cuộc gọi đồng bộ. Nhưng khi nói đến setTimeoutphương thức, nó trở thành một thực thi không đồng bộ. Nhưng ngay lập tức JS trả về từ nó và tiếp tục thực thi, được gọi là Non-Blockinghoặc Async. Và nó tiếp tục làm việc khác, vv

Kết quả của việc thực hiện này là như sau:

acdb

Vì vậy, về cơ bản, cái thứ hai setTimeoutđã hoàn thành đầu tiên và chức năng gọi lại của nó được thực hiện sớm hơn cái đầu tiên và điều đó có ý nghĩa.

Chúng ta đang nói về ứng dụng đơn luồng ở đây. Công cụ JS tiếp tục thực hiện điều này và trừ khi nó hoàn thành yêu cầu đầu tiên, nó sẽ không chuyển sang yêu cầu thứ hai. Nhưng điều tốt là nó sẽ không chờ các hoạt động chặn như setTimeoutgiải quyết để nó sẽ nhanh hơn vì nó chấp nhận các yêu cầu mới đến.

Nhưng câu hỏi của tôi phát sinh xung quanh các mục sau đây:

# 1: Nếu chúng ta đang nói về một ứng dụng đơn luồng, thì cơ chế nào xử lý setTimeoutstrong khi công cụ JS chấp nhận nhiều yêu cầu hơn và thực thi chúng? Làm thế nào để chủ đề duy nhất tiếp tục làm việc trên các yêu cầu khác? Những gì hoạt động setTimeouttrong khi các yêu cầu khác tiếp tục đến và được thực hiện.

# 2: Nếu các setTimeoutchức năng này được thực thi phía sau hậu trường trong khi có nhiều yêu cầu đến và được thực thi, điều gì sẽ thực hiện các thực thi không đồng bộ phía sau hậu trường? Cái mà chúng ta nói đến được gọi là EventLoopgì?

# 3: Nhưng không nên đặt toàn bộ phương thức EventLoopđể toàn bộ điều được thực thi và phương thức gọi lại được gọi? Đây là những gì tôi hiểu khi nói về chức năng gọi lại:

function downloadFile(filePath, callback)
{
  blah.downloadFile(filePath);
  callback();
}

Nhưng trong trường hợp này, làm thế nào để Công cụ JS biết nếu đó là một hàm async để nó có thể đặt cuộc gọi lại trong EventLoop? Perhaps something like thetừ khóa async` trong C # hoặc một loại thuộc tính nào đó cho biết phương thức mà JS Engine sẽ sử dụng là phương thức async và nên được điều trị phù hợp.

# 4: Nhưng một bài báo nói hoàn toàn trái ngược với những gì tôi đã đoán về cách mọi thứ có thể hoạt động:

Vòng lặp sự kiện là một hàng đợi các hàm gọi lại. Khi một chức năng async thực thi, chức năng gọi lại được đẩy vào hàng đợi. Công cụ JavaScript không bắt đầu xử lý vòng lặp sự kiện cho đến khi mã sau khi hàm async được thực thi.

# 5: Và có hình ảnh này ở đây có thể hữu ích nhưng lời giải thích đầu tiên trong hình ảnh là nói chính xác điều tương tự được đề cập trong câu hỏi số 4:

nhập mô tả hình ảnh ở đây

Vì vậy, câu hỏi của tôi ở đây là để có được một số làm rõ về các mục được liệt kê ở trên?


1
Chủ đề không phải là phép ẩn dụ đúng để xử lý những vấn đề đó. Nghĩ rằng các sự kiện.
Denys Séguret

1
@dystroy: Một mẫu mã để minh họa ẩn dụ sự kiện đó trong JS sẽ rất hay để xem.
Tarik

Tôi không thấy chính xác câu hỏi của bạn ở đây.
Denys Séguret

1
@dystroy: Câu hỏi của tôi ở đây là để có được một số làm rõ về các mục được liệt kê ở trên?
Tarik

2
Nút không phải là luồng đơn nhưng điều đó không quan trọng đối với bạn (ngoài thực tế là nó quản lý để thực hiện những việc khác trong khi mã người dùng của bạn thực thi). Chỉ một cuộc gọi lại nhiều nhất trong mã người dùng của bạn được thực thi cùng một lúc.
Denys Séguret

Câu trả lời:


85

1: Nếu chúng ta đang nói về một ứng dụng đơn luồng, vậy thì quá trình setTimeouts trong khi công cụ JS chấp nhận nhiều yêu cầu hơn và thực thi chúng? Không phải là chủ đề duy nhất sẽ tiếp tục làm việc trên các yêu cầu khác? Sau đó, ai sẽ tiếp tục làm việc trên setTimeout trong khi các yêu cầu khác tiếp tục đến và được thực thi.

Chỉ có 1 luồng trong quy trình nút thực sự sẽ thực thi JavaScript chương trình của bạn. Tuy nhiên, trong chính nút, thực sự có một số luồng xử lý hoạt động của cơ chế vòng lặp sự kiện và điều này bao gồm một nhóm các luồng IO và một số ít các luồng khác. Điều quan trọng là số lượng các luồng này không tương ứng với số lượng kết nối đồng thời được xử lý như trong mô hình tương tranh luồng trên mỗi kết nối.

Bây giờ về "thực thi setTimeouts", khi bạn gọi setTimeout, tất cả các nút thực hiện về cơ bản là cập nhật cấu trúc dữ liệu của các hàm sẽ được thực thi tại một thời điểm trong tương lai. Về cơ bản, nó có một loạt các hàng thứ cần làm và mỗi "đánh dấu" của vòng lặp sự kiện mà nó chọn một, loại bỏ nó khỏi hàng đợi và chạy nó.

Một điều quan trọng cần hiểu là nút phụ thuộc vào hệ điều hành trong hầu hết các công việc nặng. Vì vậy, các yêu cầu mạng đến thực sự được theo dõi bởi chính HĐH và khi nút sẵn sàng xử lý, nó chỉ sử dụng một cuộc gọi hệ thống để yêu cầu HĐH yêu cầu mạng với dữ liệu sẵn sàng được xử lý. Rất nhiều nút IO "làm việc" là "Hey OS, có kết nối mạng với dữ liệu sẵn sàng để đọc?" hoặc "Này HĐH, bất kỳ cuộc gọi hệ thống tập tin nổi bật nào của tôi đều có sẵn dữ liệu?". Dựa trên thuật toán bên trong và thiết kế công cụ vòng lặp sự kiện, nút sẽ chọn một "đánh dấu" JavaScript để thực thi, chạy nó, sau đó lặp lại quy trình một lần nữa. Đó là ý nghĩa của vòng lặp sự kiện. Node về cơ bản luôn luôn xác định "một chút JavaScript tiếp theo tôi nên chạy là gì?", Sau đó chạy nó.setTimeouthoặc process.nextTick.

2: Nếu các setTimeout này sẽ được thực hiện phía sau hậu trường trong khi có nhiều yêu cầu đến và thực hiện, thì điều thực hiện các lệnh thực thi async đằng sau hậu trường có phải là điều chúng ta đang nói về EventLoop không?

Không có JavaScript nào được thực thi phía sau hậu trường. Tất cả các JavaScript trong chương trình của bạn chạy phía trước và trung tâm, từng cái một. Điều xảy ra đằng sau hậu trường là HĐH xử lý IO và nút chờ đợi điều đó sẵn sàng và nút quản lý hàng đợi javascript của nó đang chờ thực thi.

3: Làm thế nào để JS Engine có thể biết nếu đó là một hàm async để nó có thể đưa nó vào EventLoop?

Có một tập hợp các hàm cố định trong lõi nút không đồng bộ vì chúng thực hiện các cuộc gọi hệ thống và nút biết đó là những gì vì chúng phải gọi OS hoặc C ++. Về cơ bản, tất cả các IO và hệ thống tập tin mạng cũng như các tương tác quy trình con sẽ không đồng bộ và cách CHỈ JavaScript có thể khiến nút chạy một cái gì đó không đồng bộ bằng cách gọi một trong các hàm async được cung cấp bởi thư viện lõi nút. Ngay cả khi bạn đang sử dụng gói npm xác định API của chính nó, để tạo ra vòng lặp sự kiện, cuối cùng mã của gói npm sẽ gọi một trong các hàm async của lõi nút và đó là khi nút biết dấu tick hoàn tất và nó có thể bắt đầu sự kiện thuật toán lặp lại.

4 Vòng lặp sự kiện là một hàng đợi các hàm gọi lại. Khi một chức năng async thực thi, chức năng gọi lại được đẩy vào hàng đợi. Công cụ JavaScript không bắt đầu xử lý vòng lặp sự kiện cho đến khi mã sau khi hàm async được thực thi.

Vâng, điều này là đúng, nhưng nó sai lệch. Điều quan trọng là mô hình bình thường là:

//Let's say this code is running in tick 1
fs.readFile("/home/barney/colors.txt", function (error, data) {
  //The code inside this callback function will absolutely NOT run in tick 1
  //It will run in some tick >= 2
});
//This code will absolutely also run in tick 1
//HOWEVER, typically there's not much else to do here,
//so at some point soon after queueing up some async IO, this tick
//will have nothing useful to do so it will just end because the IO result
//is necessary before anything useful can be done

Vì vậy, có, bạn hoàn toàn có thể chặn vòng lặp sự kiện bằng cách chỉ cần đếm các số Fibonacci một cách đồng bộ tất cả trong cùng một bộ nhớ, và vâng, điều đó sẽ hoàn toàn đóng băng chương trình của bạn. Đó là sự hợp tác đồng thời. Mỗi tích tắc của JavaScript phải mang lại vòng lặp sự kiện trong một khoảng thời gian hợp lý hoặc kiến ​​trúc tổng thể thất bại.


1
Hãy nói rằng tôi có một hàng đợi sẽ mất 1 phút để máy chủ thực thi và điều đầu tiên là một số chức năng không đồng bộ hoàn thành sau 10 giây. Nó sẽ đi đến cuối hàng đợi hay nó sẽ tự đẩy mình vào hàng ngay khi nó sẵn sàng?
ilyo

4
Nói chung, nó sẽ đi đến cuối hàng đợi, nhưng ngữ nghĩa của process.nextTickvs setTimeoutvs setImmediatekhác nhau một cách tinh tế, mặc dù bạn không thực sự phải quan tâm. Tôi có một bài đăng trên blog có tên setTimeout và bạn bè đi sâu vào chi tiết hơn.
Peter Lyons

Bạn có thể vui lòng giải thích? Giả sử tôi có hai cuộc gọi lại và cuộc gọi đầu tiên có phương thức ChangeColor với thời gian thực hiện là 10mS và setTimeout là 1 phút và lần thứ hai có phương thức ChangeBackground với thời gian thực hiện là 50ms với thời gian thực hiện là 10 giây. Tôi cảm thấy ChangeBackground có trong Hàng đợi trước và ChangeColor sẽ là tiếp theo. Sau đó, vòng lặp Sự kiện chọn các phương thức một cách đồng bộ. Tôi có đúng không
SheshPai

1
@SheshPai thật khó hiểu khi mọi người thảo luận về mã khi được viết bằng các đoạn tiếng Anh. Chỉ cần đăng một câu hỏi mới với một đoạn mã để mọi người có thể trả lời dựa trên mã thay vì mô tả mã, điều này để lại nhiều sự mơ hồ.
Peter Lyons

youtube.com/watch?v=QyUFheng6J0&spfreload=5 đây là một lời giải thích hay khác về JavaScript Engine
Mukesh Kumar

65

Có một video hướng dẫn tuyệt vời của Philip Roberts, giải thích vòng lặp sự kiện javascript theo cách đơn giản và khái niệm nhất. Mỗi nhà phát triển javascript nên có một cái nhìn.

Đây là liên kết video trên Youtube.


16
Tôi đã xem nó và nó thực sự là lời giải thích tốt nhất bao giờ hết.
Tarik

1
Phải xem video cho người hâm mộ và những người đam mê JavaScript.
Nirus

1
video này thay đổi cuộc sống của tôi ^^
HuyTran

1
đã đi lang thang ở đây .. và đây là một trong những lời giải thích tốt nhất tôi nhận được .. cảm ơn bạn đã chia sẻ ...: D
RohitS

1
Đó là một người xem mắt
HebleV

11

Đừng nghĩ rằng quá trình máy chủ là đơn luồng, nhưng không. Những gì đơn luồng là một phần của quá trình máy chủ thực thi mã javascript của bạn.

Ngoại trừ công nhân nền tảng , nhưng những điều này làm phức tạp kịch bản ...

Vì vậy, tất cả mã js của bạn chạy trong cùng một luồng và không có khả năng bạn có hai phần mã js khác nhau để chạy đồng thời (vì vậy, bạn không quản lý được đồng thời để quản lý).

Mã js đang thực thi là mã cuối cùng mà quá trình máy chủ chọn từ vòng lặp sự kiện. Trong mã của bạn, về cơ bản bạn có thể thực hiện hai điều: chạy các hướng dẫn đồng bộ và lên lịch các hàm sẽ được thực thi trong tương lai, khi một số sự kiện xảy ra.

Đây là đại diện tinh thần của tôi (hãy cẩn thận: chỉ là tôi không biết chi tiết triển khai trình duyệt!) Của mã ví dụ của bạn:

console.clear();                                   //exec sync
console.log("a");                                  //exec sync
setTimeout(                //schedule inAWhile to be executed at now +1 s 
    function inAWhile(){
        console.log("b");
    },1000);    
console.log("c");                                  //exec sync
setTimeout(
    function justNow(){          //schedule justNow to be executed just now
        console.log("d");
},0);       

Trong khi mã của bạn đang chạy, một luồng khác trong quy trình lưu trữ sẽ theo dõi tất cả các sự kiện hệ thống đang xảy ra (nhấp vào UI, tệp đã đọc, các gói mạng nhận được, v.v.)

Khi mã của bạn hoàn tất, nó sẽ bị xóa khỏi vòng lặp sự kiện và quá trình máy chủ quay lại kiểm tra nó, để xem có nhiều mã hơn để chạy không. Vòng lặp sự kiện chứa thêm hai trình xử lý sự kiện: một được thực thi ngay bây giờ (hàm justNow) và một vòng khác trong một giây (hàm inAWhile).

Quá trình lưu trữ bây giờ cố gắng khớp tất cả các sự kiện đã xảy ra để xem liệu có trình xử lý nào được đăng ký cho chúng không. Nó phát hiện ra rằng sự kiện mà justNow đang chờ đã xảy ra, vì vậy nó bắt đầu chạy mã của nó. Khi hàm justNow thoát, nó sẽ kiểm tra vòng lặp sự kiện vào một lần khác, tìm kiếm các trình xử lý trên các sự kiện. Giả sử 1 giây đã trôi qua, nó chạy hàm inAWhile, v.v.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.