JavaScript có được đảm bảo là một luồng không?


610

JavaScript được biết đến là đơn luồng trong tất cả các trình duyệt hiện đại triển khai, nhưng được quy định tại bất kỳ tiêu chuẩn hoặc là nó chỉ bằng cách truyền thống? Có an toàn không khi cho rằng JavaScript luôn là một luồng đơn?


25
Trong bối cảnh của các trình duyệt, có lẽ. Nhưng một số chương trình cho phép bạn coi JS như một lang cấp cao nhất và cung cấp các ràng buộc cho các lib C ++ khác. Ví dụ, flusspferd (các ràng buộc C ++ cho JS - TUYỆT VỜI BTW) đã làm một số thứ với JS đa luồng. Tùy vào bối cảnh.
NG.

Câu trả lời:


583

Đó là một câu hỏi hay. Tôi rất muốn nói tiếng vâng vâng. Tôi không thể.

JavaScript thường được coi là có một luồng thực thi duy nhất hiển thị cho các tập lệnh (*), do đó, khi tập lệnh nội tuyến, trình nghe sự kiện hoặc thời gian chờ của bạn được nhập, bạn vẫn hoàn toàn kiểm soát cho đến khi bạn quay lại từ cuối khối hoặc hàm của mình.

(*: Phớt lờ câu hỏi liệu trình duyệt thực sự thực hiện cơ JS của họ sử dụng một hệ điều hành chủ đề, hay đề-of-thực hiện hạn chế khác được giới thiệu bởi WebWorkers.)

Tuy nhiên, trong thực tế điều này không hoàn toàn đúng , theo những cách khó chịu lén lút.

Trường hợp phổ biến nhất là các sự kiện ngay lập tức. Các trình duyệt sẽ kích hoạt chúng ngay lập tức khi mã của bạn làm điều gì đó để gây ra chúng:

var l= document.getElementById('log');
var i= document.getElementById('inp');
i.onblur= function() {
    l.value+= 'blur\n';
};
setTimeout(function() {
    l.value+= 'log in\n';
    l.focus();
    l.value+= 'log out\n';
}, 100);
i.focus();
<textarea id="log" rows="20" cols="40"></textarea>
<input id="inp">

Kết quả log in, blur, log outtrên tất cả ngoại trừ IE. Những sự kiện này không chỉ xảy ra vì bạn gọi focus()trực tiếp, chúng có thể xảy ra vì bạn đã gọialert() hoặc mở cửa sổ bật lên hoặc bất kỳ thứ gì khác di chuyển trọng tâm.

Điều này cũng có thể dẫn đến các sự kiện khác. Ví dụ: thêm một i.onchangengười nghe và nhập một cái gì đó vào đầu vào trước khi focus()cuộc gọi không tập trung vào nó, và thứ tự nhật ký là log in, change, blur, log out, ngoại trừ trong Opera nơi nó log in, blur, log out, changevà IE nơi nó (thậm chí ít rõ ràng hơn) log in, change, log out, blur.

Tương tự gọi click()một phần tử cung cấp nó gọi onclicktrình xử lý ngay lập tức trong tất cả các trình duyệt (ít nhất điều này là nhất quán!).

(Tôi đang sử dụng các on...thuộc tính xử lý sự kiện trực tiếp ở đây, nhưng điều tương tự cũng xảy ra với addEventListenerattachEvent.)

Ngoài ra còn có một loạt các tình huống trong đó các sự kiện có thể phát sinh trong khi mã của bạn được xâu chuỗi, mặc dù bạn không làm để kích động nó. Một ví dụ:

var l= document.getElementById('log');
document.getElementById('act').onclick= function() {
    l.value+= 'alert in\n';
    alert('alert!');
    l.value+= 'alert out\n';
};
window.onresize= function() {
    l.value+= 'resize\n';
};
<textarea id="log" rows="20" cols="40"></textarea>
<button id="act">alert</button>

Lượt alertvà bạn sẽ nhận được một hộp thoại phương thức. Không có kịch bản thực thi cho đến khi bạn bỏ qua cuộc đối thoại, có? Không. Thay đổi kích thước cửa sổ chính và bạn sẽ nhận được alert in, resize, alert outtrong textarea.

Bạn có thể nghĩ rằng không thể thay đổi kích thước cửa sổ trong khi hộp thoại chế độ bật lên, nhưng không phải vậy: trong Linux, bạn có thể thay đổi kích thước cửa sổ bao nhiêu tùy thích; trên Windows không dễ dàng gì, nhưng bạn có thể làm điều đó bằng cách thay đổi độ phân giải màn hình từ lớn hơn sang nhỏ hơn khi cửa sổ không vừa, khiến nó bị thay đổi kích thước.

Bạn có thể nghĩ, tốt, nó chỉ resize(và có lẽ một vài giống nhưscroll ) có thể kích hoạt khi người dùng không có tương tác tích cực với trình duyệt vì tập lệnh được xâu chuỗi. Và đối với các cửa sổ duy nhất bạn có thể đúng. Nhưng tất cả sẽ đi vào nồi ngay khi bạn đang thực hiện kịch bản chéo cửa sổ. Đối với tất cả các trình duyệt khác ngoài Safari, chặn tất cả các cửa sổ / tab / khung khi bất kỳ trình duyệt nào bận, bạn có thể tương tác với một tài liệu từ mã của tài liệu khác, chạy trong một luồng thực thi riêng biệt và khiến bất kỳ trình xử lý sự kiện liên quan nào ngọn lửa.

Các địa điểm nơi các sự kiện mà bạn có thể gây ra có thể được tạo ra trong khi tập lệnh vẫn được xâu chuỗi:

  • khi popup modal ( alert, confirm, prompt) được mở, trong tất cả các trình duyệt nhưng Opera;

  • trong showModalDialogcác trình duyệt hỗ trợ nó;

  • Tập lệnh của A trên trang này có thể đang bận ... Hộp thoại hội thoại, ngay cả khi bạn chọn để tập lệnh tiếp tục chạy, cho phép các sự kiện như thay đổi kích thước và làm mờ để kích hoạt và được xử lý ngay cả khi tập lệnh nằm ở giữa bận vòng lặp, ngoại trừ trong Opera.

  • Cách đây một thời gian đối với tôi, trong IE với Plugin Sun Java, việc gọi bất kỳ phương thức nào trên một applet có thể cho phép các sự kiện được kích hoạt và tập lệnh được nhập lại. Đây luôn là một lỗi nhạy cảm với thời gian và có thể Sun đã sửa nó từ đó (tôi chắc chắn hy vọng vậy).

  • có lẽ nhiều hơn Đã được một thời gian kể từ khi tôi thử nghiệm điều này và các trình duyệt đã trở nên phức tạp kể từ đó.

Tóm lại, JavaScript xuất hiện với hầu hết người dùng, hầu hết thời gian, có một luồng thực thi duy nhất theo hướng sự kiện nghiêm ngặt. Trong thực tế, nó không có điều đó. Không rõ bao nhiêu trong số này chỉ đơn giản là một lỗi và bao nhiêu thiết kế có chủ ý, nhưng nếu bạn đang viết các ứng dụng phức tạp, đặc biệt là các ứng dụng kịch bản chéo cửa sổ, có nhiều khả năng nó có thể cắn bạn - và không liên tục, những cách khó để gỡ lỗi.

Nếu điều tồi tệ nhất đến tồi tệ nhất, bạn có thể giải quyết các vấn đề tương tranh bằng cách gián tiếp tất cả các phản ứng sự kiện. Khi một sự kiện đến, thả nó vào hàng đợi và xử lý hàng đợi theo thứ tự sau, trong mộtsetInterval hàm. Nếu bạn đang viết một khung mà bạn dự định sẽ được sử dụng bởi các ứng dụng phức tạp, thì việc này có thể là một bước đi tốt. postMessagecũng hy vọng sẽ làm dịu nỗi đau của kịch bản tài liệu chéo trong tương lai.


14
@JP: Cá nhân tôi không muốn biết ngay vì điều đó có nghĩa là tôi phải cẩn thận rằng mã của tôi được cấp lại, việc gọi mã mờ của tôi sẽ không ảnh hưởng đến trạng thái mà một số mã bên ngoài đang dựa vào. Có quá nhiều trường hợp làm mờ là một tác dụng phụ không mong muốn để bắt tất cả mọi người. Và thật không may ngay cả khi bạn làm muốn nó, nó không phải là đáng tin cậy! IE kích hoạt blur sau khi mã của bạn trả lại quyền kiểm soát cho trình duyệt.
bobince

107
Javascript là một luồng đơn. Dừng thực thi của bạn trên cảnh báo () không có nghĩa là luồng sự kiện dừng bơm sự kiện. Chỉ có nghĩa là tập lệnh của bạn đang ngủ trong khi cảnh báo ở trên màn hình, nhưng nó phải tiếp tục bơm các sự kiện để vẽ màn hình. Trong khi có cảnh báo, máy bơm sự kiện đang chạy, điều đó có nghĩa là nó hoàn toàn chính xác để tiếp tục gửi các sự kiện. Tốt nhất điều này cho thấy một luồng hợp tác có thể xảy ra trong javascript, nhưng tất cả các hành vi này có thể được giải thích bằng một chức năng chỉ đơn giản là nối thêm một sự kiện vào máy bơm sự kiện để xử lý tại thời điểm sau so với thực hiện ngay bây giờ.
chubbsondub

34
Nhưng, hãy nhớ luồng hợp tác vẫn là luồng đơn. Hai điều không thể xảy ra đồng thời, đó là những gì đa luồng cho phép và tiêm không xác định. Tất cả những gì được mô tả là có tính quyết định và đó là một lời nhắc tốt về các loại vấn đề này. Làm tốt công việc phân tích
@bobince

94
Chubbard là đúng: JavaScript là luồng đơn. Đây không phải là một ví dụ về đa luồng, mà là gửi tin nhắn đồng bộ trong một luồng. Có, có thể tạm dừng ngăn xếp và tiếp tục gửi sự kiện (ví dụ: alert ()), nhưng các loại sự cố truy cập xảy ra trong môi trường đa luồng thực sự đơn giản là không thể xảy ra; ví dụ, bạn sẽ không bao giờ có giá trị thay đổi thay đổi giữa bạn giữa bài kiểm tra và bài tập tiếp theo ngay lập tức, bởi vì luồng của bạn không thể bị gián đoạn tùy ý. Tôi sợ rằng phản ứng này sẽ chỉ gây ra nhầm lẫn.
Kris Giesing

19
Có, nhưng do chức năng chặn chờ đầu vào của người dùng có thể xảy ra ở giữa hai câu lệnh bất kỳ, bạn có khả năng xảy ra tất cả các vấn đề về tính nhất quán mà các luồng cấp độ hệ điều hành mang lại cho bạn. Liệu công cụ JavaScript có thực sự chạy trong nhiều luồng hệ điều hành hay không ít liên quan.
bobince

115

Tôi muốn nói có - bởi vì hầu như tất cả các mã javascript hiện có (ít nhất là không tầm thường) sẽ bị hỏng nếu công cụ javascript của trình duyệt chạy không đồng bộ.

Thêm vào đó, thực tế là HTML5 đã chỉ định Công nhân web (API rõ ràng, được tiêu chuẩn hóa cho mã javascript đa luồng) giới thiệu đa luồng vào Javascript cơ bản sẽ hầu như vô nghĩa.

( Lưu ý với người khác bình luận: Mặc dùsetTimeout/setInterval , các sự kiện tải về yêu cầu HTTP (XHR) và các sự kiện UI (nhấp, tập trung, v.v.) cung cấp một ấn tượng thô thiển về tính đa luồng - tất cả chúng vẫn được thực hiện theo một dòng thời gian duy nhất - một tại một thời gian - vì vậy ngay cả khi chúng ta không biết thứ tự thực hiện của chúng trước đó, không cần phải lo lắng về các điều kiện bên ngoài thay đổi trong quá trình thực thi trình xử lý sự kiện, chức năng hẹn giờ hoặc gọi lại XHR.)


21
Tôi đồng ý. Nếu đa luồng được thêm vào Javascript trong trình duyệt, nó sẽ thông qua một số API rõ ràng (ví dụ: Công nhân web) giống như với tất cả các ngôn ngữ bắt buộc. Đó là cách duy nhất có ý nghĩa.
Dean Harding

1
Lưu ý rằng có một. chủ đề JS chính, NHƯNG một số thứ được chạy song song trong trình duyệt. Nó không chỉ là một ấn tượng của đa luồng. Yêu cầu thực sự được chạy song song. Các trình nghe mà bạn xác định trong JS được chạy từng cái một, nhưng các yêu cầu thực sự song song.
Nux

16

Có, mặc dù bạn vẫn có thể gặp phải một số vấn đề về lập trình đồng thời (chủ yếu là điều kiện chủng tộc) khi sử dụng bất kỳ API không đồng bộ nào như các cuộc gọi lại setInterval và xmlhttp.


10

Có, mặc dù Internet Explorer 9 sẽ biên dịch Javascript của bạn trên một luồng riêng biệt để chuẩn bị thực hiện trên luồng chính. Điều này không thay đổi bất cứ điều gì cho bạn như một lập trình viên, mặc dù.


8

Tôi có thể nói rằng các đặc điểm kỹ thuật không ngăn cản ai đó từ việc tạo ra một động cơchạy JavaScript trên nhiều chủ đề , đòi hỏi mã để thực hiện đồng bộ để truy cập vào trạng thái đối tượng chia sẻ.

Tôi nghĩ rằng mô hình không chặn đơn luồng xuất phát từ nhu cầu chạy javascript trong các trình duyệt mà ui không bao giờ nên chặn.

Nodejs đã làm theo cách tiếp cận của trình duyệt .

Tuy nhiên, công cụ Rhino hỗ trợ chạy mã js trong các luồng khác nhau . Việc thực thi không thể chia sẻ bối cảnh, nhưng chúng có thể chia sẻ phạm vi. Đối với trường hợp cụ thể này, tài liệu nêu rõ:

... "Rhino đảm bảo rằng việc truy cập vào các thuộc tính của các đối tượng JavaScript là nguyên tử trên các luồng, nhưng không đảm bảo thêm cho các tập lệnh thực thi trong cùng một phạm vi cùng một lúc. Nếu hai tập lệnh sử dụng cùng một phạm vi, các tập lệnh là chịu trách nhiệm điều phối mọi truy cập vào các biến được chia sẻ . "

Từ việc đọc tài liệu của Rhino, tôi kết luận rằng có thể ai đó có thể viết một api javascript cũng sinh ra các luồng javascript mới, nhưng api sẽ đặc trưng cho tê giác (ví dụ: nút chỉ có thể tạo ra một quy trình mới).

Tôi tưởng tượng rằng ngay cả đối với một công cụ hỗ trợ nhiều luồng trong javascript cũng phải có khả năng tương thích với các tập lệnh không xem xét đa luồng hoặc chặn.

Concearning trình duyệtnodejs cách tôi nhìn thấy nó là:

    1. tất cảjs được thực thi trong một chủ đề ? : Vâng
    1. js có thể gây ra các chủ đề khác để chạy ? : Vâng
    1. Các chủ đề này có thể thay đổi bối cảnh thực thi js không ?: Không. Nhưng chúng có thể (trực tiếp / gián tiếp (?)) Nối vào hàng đợi sự kiện mà từ đó người nghe có thể thay đổi bối cảnh thực hiện . Nhưng đừng để bị lừa, người nghe chạy nguyên bản trên luồng chính .

Vì vậy, trong trường hợp trình duyệt và nodejs (và có thể rất nhiều công cụ khác), javascript không phải là đa luồng mà chính các công cụ đó .


Cập nhật về nhân viên web:

Sự hiện diện của các nhân viên web chứng minh thêm rằng javascript có thể đa luồng, theo nghĩa là ai đó có thể tạo mã trong javascript sẽ chạy trên một luồng riêng biệt.

Tuy nhiên: nhân viên web không xử lý các vấn đề của các luồng truyền thống có thể chia sẻ bối cảnh thực thi. Quy tắc 2 và 3 ở trên vẫn được áp dụng , nhưng lần này mã luồng được tạo bởi người dùng (người viết mã js) trong javascript.

Điều duy nhất cần xem xét là số lượng các chủ đề được sinh ra, từ quan điểm hiệu quả (và không đồng thời ). Xem bên dưới:

Về an toàn chủ đề :

Giao diện Worker sinh ra các luồng thực sự ở cấp độ hệ điều hành và các lập trình viên có tâm có thể lo ngại rằng sự tương tranh có thể gây ra các hiệu ứng Thú vị trong mã của bạn nếu bạn không cẩn thận.

Tuy nhiên, vì nhân viên web đã kiểm soát cẩn thận các điểm giao tiếp với các luồng khác, nên thực sự rất khó gây ra sự cố đồng thời . Không có quyền truy cập vào các thành phần không an toàn hoặc DOM. Và bạn phải truyền dữ liệu cụ thể vào và ra khỏi một luồng thông qua các đối tượng được tuần tự hóa. Vì vậy, bạn phải làm việc rất chăm chỉ để gây ra vấn đề trong mã của bạn.


PS

Bên cạnh lý thuyết, luôn luôn chuẩn bị về các trường hợp góc và lỗi có thể được mô tả trên câu trả lời được chấp nhận


7

JavaScript / ECMAScript được thiết kế để sống trong môi trường máy chủ. Đó là, JavaScript thực sự không làm gì cả trừ khi môi trường máy chủ quyết định phân tích cú pháp và thực thi một tập lệnh đã cho và cung cấp các đối tượng môi trường cho phép JavaScript thực sự hữu ích (chẳng hạn như DOM trong trình duyệt).

Tôi nghĩ rằng một chức năng hoặc khối tập lệnh đã cho sẽ thực thi từng dòng một và điều đó được đảm bảo cho JavaScript. Tuy nhiên, có lẽ một môi trường máy chủ có thể thực thi nhiều tập lệnh cùng một lúc. Hoặc, một môi trường máy chủ luôn có thể cung cấp một đối tượng cung cấp đa luồng. setTimeoutsetIntervallà các ví dụ, hoặc ít nhất là các ví dụ giả, về môi trường máy chủ cung cấp một cách để thực hiện một số đồng thời (ngay cả khi nó không chính xác đồng thời).


7

Trên thực tế, một cửa sổ cha mẹ có thể giao tiếp với các cửa sổ con hoặc anh chị em hoặc các khung có các luồng thực thi riêng của chúng đang chạy.


6

@Bobince đang cung cấp một câu trả lời thực sự mờ đục.

Bỏ qua câu trả lời của Már rlygieson, Javascript luôn là một luồng đơn vì thực tế đơn giản này: Mọi thứ trong Javascript được thực thi theo một dòng thời gian duy nhất.

Đó là định nghĩa chặt chẽ của ngôn ngữ lập trình đơn luồng.


4

Không.

Tôi sẽ chống lại đám đông ở đây, nhưng chịu đựng tôi. Một tập lệnh JS duy nhất được dự định là một luồng hiệu quả , nhưng điều này không có nghĩa là nó không thể được hiểu khác nhau.

Giả sử bạn có mã sau đây ...

var list = [];
for (var i = 0; i < 10000; i++) {
  list[i] = i * i;
}

Điều này được viết với kỳ vọng rằng vào cuối vòng lặp, danh sách phải có 10000 mục nhập là bình phương chỉ mục, nhưng VM có thể nhận thấy rằng mỗi lần lặp của vòng lặp không ảnh hưởng đến vòng lặp khác và diễn giải lại bằng hai luồng.

Chủ đề đầu tiên

for (var i = 0; i < 5000; i++) {
  list[i] = i * i;
}

Chủ đề thứ hai

for (var i = 5000; i < 10000; i++) {
  list[i] = i * i;
}

Tôi đang đơn giản hóa ở đây, vì các mảng JS phức tạp hơn nên các bộ nhớ câm, nhưng nếu hai tập lệnh này có thể thêm các mục vào mảng theo cách an toàn luồng, thì đến lúc cả hai thực hiện xong thì nó sẽ có kết quả tương tự như phiên bản đơn luồng.

Mặc dù tôi không biết về bất kỳ máy ảo nào phát hiện mã song song như thế này, có vẻ như nó có thể tồn tại trong tương lai cho máy ảo JIT, vì nó có thể cung cấp nhiều tốc độ hơn trong một số trường hợp.

Đưa khái niệm này đi xa hơn, có thể mã có thể được chú thích để cho VM biết những gì cần chuyển đổi thành mã đa luồng.

// like "use strict" this enables certain features on compatible VMs.
"use parallel";

var list = [];

// This string, which has no effect on incompatible VMs, enables threading on
// this loop.
"parallel for";
for (var i = 0; i < 10000; i++) {
  list[i] = i * i;
}

Vì các Công nhân Web đang đến với Javascript, nên chắc chắn rằng ... hệ thống xấu hơn này sẽ tồn tại, nhưng tôi nghĩ rằng Javascript an toàn khi nói theo truyền thống.


2
Hầu hết các định nghĩa ngôn ngữ được thiết kế để đơn luồng một cách hiệu quả và nói rõ rằng đa luồng được cho phép miễn là hiệu ứng này giống hệt nhau. (ví dụ như UML)
jevon

1
Tôi phải đồng ý với câu trả lời đơn giản vì ECMAScript hiện tại không đưa ra điều khoản nào (mặc dù tôi cho rằng điều tương tự có thể được nói cho C) đối với bối cảnh thực thi ECMAScript đồng thời . Sau đó, giống như câu trả lời này, tôi lập luận rằng bất kỳ triển khai nào có các luồng đồng thời có thể sửa đổi trạng thái chia sẻ, là một phần mở rộng ECMAScript .
dùng2864740

3

Chà, Chrome là đa xử lý và tôi nghĩ mọi quy trình đều xử lý mã Javascript riêng của mình, nhưng theo như mã biết, thì đó là "đơn luồng".

Không có hỗ trợ nào trong Javascript cho đa luồng, ít nhất là không rõ ràng, vì vậy nó không tạo ra sự khác biệt.


2

Tôi đã thử ví dụ của @ bobince với một chút sửa đổi:

<html>
<head>
    <title>Test</title>
</head>
<body>
    <textarea id="log" rows="20" cols="40"></textarea>
    <br />
    <button id="act">Run</button>
    <script type="text/javascript">
        let l= document.getElementById('log');
        let b = document.getElementById('act');
        let s = 0;

        b.addEventListener('click', function() {
            l.value += 'click begin\n';

            s = 10;
            let s2 = s;

            alert('alert!');

            s = s + s2;

            l.value += 'click end\n';
            l.value += `result = ${s}, should be ${s2 + s2}\n`;
            l.value += '----------\n';
        });

        window.addEventListener('resize', function() {
            if (s === 10) {
                s = 5;
            }

            l.value+= 'resize\n';
        });
    </script>
</body>
</html>

Vì vậy, khi bạn nhấn Run, đóng cửa sổ bật lên cảnh báo và thực hiện một "chủ đề duy nhất", bạn sẽ thấy một cái gì đó như thế này:

click begin
click end
result = 20, should be 20

Nhưng nếu bạn cố chạy cái này trong Opera hoặc Firefox ổn định trên Windows và thu nhỏ / tối đa hóa cửa sổ với cửa sổ bật lên cảnh báo trên màn hình, thì sẽ có một cái gì đó như thế này:

click begin
resize
click end
result = 15, should be 20

Tôi không muốn nói rằng đây là "đa luồng", nhưng một số đoạn mã đã thực thi sai thời điểm với tôi không mong đợi điều này, và bây giờ tôi có trạng thái bị hỏng. Và tốt hơn để biết về hành vi này.


-4

Cố gắng lồng hai hàm setTimeout vào nhau và chúng sẽ hoạt động đa luồng (nghĩa là bộ định thời bên ngoài sẽ không đợi hàm bên trong hoàn thành trước khi thực hiện chức năng của nó).


5
chrome thực hiện điều này một cách đúng đắn, dunno nơi @ James nhìn thấy nó được đa luồng ...: setTimeout(function(){setTimeout(function(){console.log('i herd you liek async')}, 0); alert('yo dawg!')}, 0)(đối với hồ sơ, yo Dawg nên luôn luôn đi trước, sau đó sản lượng console log)
Tor Valamo
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.