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?
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?
Câu trả lời:
Đó 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 out
trê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.onchange
ngườ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, change
và 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 onclick
trì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 addEventListener
và attachEvent
.)
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 gì để 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 alert
và 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 out
trong 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 showModalDialog
cá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. postMessage
cũ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.
blur
sau khi mã của bạn trả lại quyền kiểm soát cho trình duyệt.
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.)
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.
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ơ mà 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ệt và nodejs cách tôi nhìn thấy nó là:
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ụ đó .
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:
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.
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
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. setTimeout
và setInterval
là 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).
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.
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.
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.
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ó).
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)