Apakah JavaScript dijamin utas tunggal?

611

JavaScript dikenal sebagai single-threaded di semua implementasi browser modern, tetapi apakah itu ditentukan dalam standar apa pun atau apakah itu hanya berdasarkan tradisi? Apakah benar-benar aman untuk mengasumsikan bahwa JavaScript selalu menggunakan utas tunggal?

Egor Pavlikhin
sumber
25
Dalam konteks browser, mungkin. Tetapi beberapa program memungkinkan Anda untuk memperlakukan JS sebagai bahasa tingkat atas dan menyediakan binding untuk lib C ++ lainnya. Sebagai contoh, flusspferd (binding C ++ untuk JS - AWESOME BTW) melakukan beberapa hal dengan multithreaded JS. Terserah konteksnya.
NG.

Jawaban:

583

Itu pertanyaan yang bagus. Saya ingin mengatakan "ya". Saya tidak bisa.

JavaScript biasanya dianggap memiliki satu utas eksekusi yang terlihat oleh skrip (*), sehingga ketika skrip inline, pendengar acara, atau batas waktu Anda dimasukkan, Anda tetap memegang kendali sepenuhnya sampai Anda kembali dari ujung blok atau fungsi Anda.

(*: mengabaikan pertanyaan apakah browser benar-benar mengimplementasikan mesin JS mereka menggunakan satu OS-thread, atau apakah thread-of-eksekusi terbatas lainnya diperkenalkan oleh WebWorkers.)

Namun, pada kenyataannya ini tidak sepenuhnya benar , dengan cara licik yang jahat.

Kasus yang paling umum adalah kejadian langsung. Peramban akan memecat ini segera ketika kode Anda melakukan sesuatu yang menyebabkannya:

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">

Menghasilkan log in, blur, log outsemua kecuali IE. Peristiwa ini tidak hanya terjadi karena Anda menelepon focus()langsung, tetapi bisa juga terjadi karena Anda menelepon alert(), atau membuka jendela sembul, atau apa pun yang menggerakkan fokus.

Ini juga dapat menyebabkan acara lain. Sebagai contoh, tambahkan i.onchangependengar dan ketik sesuatu di input sebelum focus()panggilan tidak fokus, dan urutan log adalah log in, change, blur, log out, kecuali di Opera di mana itu log in, blur, log out, changedan IE di mana itu (bahkan kurang jelas) log in, change, log out, blur.

Demikian pula memanggil click()elemen yang menyediakannya memanggil onclickpenangan segera di semua browser (setidaknya ini konsisten!).

(Saya menggunakan on...properti event handler langsung di sini, tetapi hal yang sama terjadi pada addEventListenerdan attachEvent.)

Ada juga banyak keadaan di mana peristiwa dapat memanas saat kode Anda masuk, meskipun Anda tidak melakukan apa pun untuk memprovokasi. Sebuah contoh:

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>

Tekan alertdan Anda akan mendapatkan kotak dialog modal. Tidak ada lagi skrip yang dijalankan hingga Anda menolak dialog itu, ya? Nggak. Ubah ukuran jendela utama dan Anda akan mendapatkan alert in, resize, alert outdi textarea.

Anda mungkin berpikir tidak mungkin untuk mengubah ukuran jendela sementara kotak dialog modal naik, tetapi tidak demikian: di Linux, Anda dapat mengubah ukuran jendela sebanyak yang Anda inginkan; pada Windows itu tidak mudah, tetapi Anda bisa melakukannya dengan mengubah resolusi layar dari yang lebih besar ke yang lebih kecil di mana jendela tidak muat, menyebabkan ukurannya kembali.

Anda mungkin berpikir, yah, itu hanya resize(dan mungkin beberapa lebih seperti scroll) yang dapat diaktifkan ketika pengguna tidak memiliki interaksi aktif dengan browser karena skrip di-threaded. Dan untuk satu jendela Anda mungkin benar. Tapi itu semua menjadi pot segera setelah Anda melakukan skrip lintas-jendela. Untuk semua browser selain Safari, yang memblokir semua jendela / tab / bingkai ketika salah satu dari mereka sibuk, Anda dapat berinteraksi dengan dokumen dari kode dokumen lain, berjalan di utas terpisah dari eksekusi dan menyebabkan penangan acara terkait untuk api.

Tempat-tempat di mana peristiwa yang Anda dapat hasilkan dapat dimunculkan saat skrip masih berulir:

  • ketika popup modal ( alert, confirm, prompt) terbuka, di semua browser tapi Opera;

  • selama showModalDialogdi browser yang mendukungnya;

  • kotak dialog "Skrip pada halaman ini mungkin sibuk ...", bahkan jika Anda memilih untuk membiarkan skrip terus berjalan, memungkinkan acara seperti mengubah ukuran dan memburamkan dan ditangani bahkan ketika skrip berada di tengah-tengah busy-loop, kecuali di Opera.

  • beberapa waktu yang lalu bagi saya, di IE dengan Sun Java Plugin, memanggil metode apa pun pada applet dapat memungkinkan acara untuk diaktifkan dan skrip dimasukkan kembali. Ini selalu bug yang sensitif terhadap waktu, dan mungkin Sun telah memperbaikinya sejak itu (saya tentu berharap demikian)

  • mungkin lebih. Sudah lama sejak saya menguji ini dan browser telah mendapatkan kompleksitas sejak itu.

Singkatnya, JavaScript tampaknya bagi sebagian besar pengguna, sebagian besar waktu, memiliki alur eksekusi tunggal yang ketat berdasarkan peristiwa. Pada kenyataannya, itu tidak memiliki hal seperti itu. Tidak jelas seberapa banyak ini hanyalah bug dan seberapa banyak disengaja desain, tetapi jika Anda menulis aplikasi yang kompleks, terutama yang cross-window / frame-scripting, ada setiap kesempatan itu bisa menggigit Anda - dan secara intermiten, cara sulit untuk debug.

Jika yang terburuk menjadi yang terburuk, Anda dapat memecahkan masalah konkurensi dengan mengarahkan semua respons peristiwa. Ketika suatu peristiwa masuk, letakkan dalam antrian dan urus dengan antrian nanti, dalam suatu setIntervalfungsi. Jika Anda menulis kerangka kerja yang Anda maksudkan untuk digunakan oleh aplikasi yang kompleks, melakukan ini bisa menjadi langkah yang baik. postMessagesemoga juga akan meringankan rasa sakit scripting lintas dokumen di masa depan.

bobince
sumber
14
@ JP: Secara pribadi saya tidak ingin langsung tahu karena itu berarti saya harus berhati-hati bahwa kode saya masuk kembali, bahwa panggilan kode blur saya tidak akan mempengaruhi keadaan bahwa beberapa kode luar mengandalkan. Ada terlalu banyak kasus di mana keburaman merupakan efek samping yang tidak diharapkan untuk menangkap setiap orang. Dan sayangnya bahkan jika Anda melakukannya menginginkannya, itu tidak dapat diandalkan! IE menyala blur setelah kode Anda mengembalikan kontrol ke browser.
bobince
107
Javascript adalah utas tunggal. Menghentikan eksekusi Anda pada peringatan () tidak berarti utas acara berhenti memompa acara. Artinya skrip Anda sedang tidur saat peringatan ada di layar, tetapi skrip harus terus memompa acara untuk menggambar layar. Ketika peringatan menyala, pompa peristiwa sedang berjalan yang artinya sepenuhnya benar untuk terus mengirimkan acara. Paling-paling ini menunjukkan threading kooperatif yang dapat terjadi dalam javascript, tetapi semua perilaku ini dapat dijelaskan oleh fungsi yang hanya menambahkan acara ke pompa acara untuk diproses pada titik kemudian vs melakukannya sekarang.
chubbsondubs
34
Tapi, ingat threading kooperatif masih single threaded. Dua hal tidak dapat terjadi secara bersamaan yang memungkinkan multi-threading dan menyuntikkan non-determinisme. Semua yang dideskripsikan bersifat deterministik, dan ini merupakan pengingat yang baik tentang jenis masalah ini.
Kerja
94
Chubbard benar: JavaScript adalah utas tunggal. Ini bukan contoh multithreading, tetapi pengiriman pesan sinkron dalam satu utas. Ya, mungkin untuk menjeda tumpukan dan pengiriman acara terus (mis. Waspada ()), tetapi jenis masalah akses yang terjadi di lingkungan multithreaded benar tidak bisa terjadi; misalnya, Anda tidak akan pernah memiliki nilai perubahan variabel pada Anda antara tes dan tugas berikutnya segera, karena utas Anda tidak dapat secara sewenang-wenang terputus. Saya khawatir respons ini hanya akan menimbulkan kebingungan.
Kris Giesing
19
Ya, tetapi mengingat bahwa fungsi pemblokiran yang menunggu input pengguna dapat terjadi di antara dua pernyataan, Anda berpotensi memiliki semua masalah konsistensi yang diberikan oleh thread-level OS kepada Anda. Apakah mesin JavaScript benar-benar berjalan di beberapa utas OS adalah sedikit relevansinya.
bobince
115

Saya akan mengatakan ya - karena hampir semua kode javascript yang ada (setidaknya semua non-sepele) akan rusak jika mesin javascript browser menjalankannya secara tidak sinkron.

Tambahkan ke fakta bahwa HTML5 sudah menentukan Pekerja Web (eksplisit, standar API untuk kode javascript multi-threading) memperkenalkan multi-threading ke dalam Javascript dasar akan sebagian besar tidak ada gunanya.

( Catatan untuk komentator lain: Meskipun setTimeout/setInterval, peristiwa onload permintaan HTTP (XHR), dan peristiwa UI (klik, fokus, dll.) Memberikan kesan kasar tentang multi-utas - semuanya masih dijalankan dalam satu timeline - satu di suatu waktu - jadi bahkan jika kita tidak tahu urutan eksekusi mereka sebelumnya, tidak perlu khawatir tentang perubahan kondisi eksternal selama eksekusi pengendali event, fungsi waktunya atau panggilan balik XHR.)

Már Örlygsson
sumber
21
Saya setuju. Jika multi-threading pernah ditambahkan ke Javascript di browser, itu akan melalui beberapa API eksplisit (misalnya Pekerja Web) seperti halnya dengan semua bahasa penting. Itulah satu-satunya cara yang masuk akal.
Dean Harding
1
Perhatikan bahwa ada satu. utas JS utama, TETAPI beberapa hal dijalankan di browser secara paralel. Ini bukan hanya kesan multi-utas. Permintaan sebenarnya berjalan secara paralel. Pendengar yang Anda tetapkan dalam JS dijalankan satu per satu, tetapi permintaannya benar-benar paralel.
Nux
16

Ya, meskipun Anda masih dapat mengalami beberapa masalah pemrograman bersamaan (terutama kondisi ras) saat menggunakan salah satu API asinkron seperti setInterval dan callback xmlhttp.

pemboros
sumber
10

Ya, meskipun Internet Explorer 9 akan mengkompilasi Javascript Anda pada utas terpisah sebagai persiapan untuk eksekusi pada utas utama. Ini tidak mengubah apa pun untuk Anda sebagai programmer.

ChessWhiz
sumber
8

Saya akan mengatakan bahwa spesifikasi tidak mencegah seseorang membuat mesin yang menjalankan javascript pada banyak utas , memerlukan kode untuk melakukan sinkronisasi untuk mengakses keadaan objek bersama.

Saya pikir paradigma non-blocking single-threaded keluar dari kebutuhan untuk menjalankan javascript di browser di mana ui tidak boleh memblokir.

Nodejs telah mengikuti pendekatan peramban .

Mesin badak , mendukung menjalankan kode js di utas berbeda . Eksekusi tidak bisa berbagi konteks, tetapi mereka bisa berbagi ruang lingkup. Untuk kasus khusus ini, dokumentasi menyatakan:

... "Badak menjamin bahwa akses ke properti objek JavaScript adalah atom di seluruh utas, tetapi tidak membuat jaminan lagi untuk skrip yang mengeksekusi dalam lingkup yang sama pada saat yang sama. Jika dua skrip menggunakan cakupan yang sama secara bersamaan, skrip tersebut adalah bertanggung jawab untuk mengoordinasikan setiap akses ke variabel yang dibagi . "

Dari membaca dokumentasi Rhino saya berkesimpulan bahwa seseorang dapat menulis api javascript yang juga dapat menghasilkan untaian javascript baru, tetapi api tersebut akan khusus badak (misal, simpul hanya dapat memunculkan proses baru).

Saya membayangkan bahwa bahkan untuk mesin yang mendukung banyak utas dalam javascript harus ada kompatibilitas dengan skrip yang tidak mempertimbangkan multi-threading atau pemblokiran.

Menyembunyikan browser dan simpul seperti yang saya lihat adalah:

    1. Apakah semua kode js dieksekusi dalam satu utas ? : Ya.
    1. Bisakah kode js menyebabkan utas lain berjalan ? : Ya.
    1. Bisa benang ini konteks eksekusi bermutasi js :? Tidak. Tapi mereka bisa (langsung / tidak langsung ()?) Append ke antrian acara dari mana pendengar dapat bermutasi konteks eksekusi . Tapi jangan tertipu, pendengar berjalan secara atom pada utas utama lagi.

Jadi, dalam kasus browser dan nodejs (dan mungkin banyak mesin lainnya) javascript tidak multithreaded tetapi mesin itu sendiri .


Pembaruan tentang pekerja web:

Kehadiran pekerja web membenarkan lebih lanjut bahwa javascript dapat multi-threaded, dalam arti bahwa seseorang dapat membuat kode dalam javascript yang akan dijalankan pada utas terpisah.

Namun: pekerja web tidak menjelajah masalah utas tradisional yang dapat berbagi konteks eksekusi. Aturan 2 dan 3 di atas masih berlaku , tetapi kali ini kode ulir dibuat oleh pengguna (penulis kode js) di javascript.

Satu-satunya hal yang perlu dipertimbangkan adalah jumlah untaian yang muncul, dari sudut pandang efisiensi (dan bukan konkurensi ). Lihat di bawah:

Tentang keamanan benang :

Antarmuka Worker memunculkan thread tingkat OS nyata, dan programmer yang peduli mungkin khawatir bahwa concurrency dapat menyebabkan efek "menarik" dalam kode Anda jika Anda tidak hati-hati.

Namun, karena pekerja web telah dengan hati-hati mengontrol titik komunikasi dengan utas lain, sebenarnya sangat sulit untuk menyebabkan masalah konkurensi . Tidak ada akses ke komponen non-threadsafe atau DOM. Dan Anda harus melewati data spesifik masuk dan keluar dari utas melalui objek serial. Jadi, Anda harus bekerja sangat keras untuk menyebabkan masalah dalam kode Anda.


PS

Selain teori, selalu siap tentang kemungkinan kasus sudut dan bug yang dijelaskan pada jawaban yang diterima

Marinos An
sumber
7

JavaScript / ECMAScript dirancang untuk hidup dalam lingkungan host. Artinya, JavaScript tidak benar - benar melakukan apa pun kecuali lingkungan host memutuskan untuk mem-parsing dan mengeksekusi skrip yang diberikan, dan menyediakan objek lingkungan yang membiarkan JavaScript benar-benar bermanfaat (seperti DOM di browser).

Saya pikir fungsi atau blok skrip yang diberikan akan mengeksekusi baris demi baris dan itu dijamin untuk JavaScript. Namun, mungkin lingkungan host dapat menjalankan beberapa skrip sekaligus. Atau, lingkungan host selalu dapat menyediakan objek yang menyediakan multi-threading. setTimeoutdan setIntervalmerupakan contoh, atau setidaknya contoh pseudo, dari lingkungan host yang menyediakan cara untuk melakukan beberapa konkurensi (bahkan jika itu bukan konkurensi).

Bob
sumber
7

Sebenarnya, jendela induk dapat berkomunikasi dengan jendela anak atau saudara kandung atau bingkai yang menjalankan utas eksekusi sendiri.

kennebec
sumber
6

@Bobince memberikan jawaban yang benar-benar buram.

Mengganti jawaban Már Örlygsson, Javascript selalu berurutan tunggal karena fakta sederhana ini: Segala sesuatu di Javascript dieksekusi sepanjang satu garis waktu.

Itu adalah definisi ketat dari bahasa pemrograman single-threaded.

wle8300
sumber
4

Tidak.

Aku akan melawan orang banyak di sini, tapi bersabarlah. Script JS tunggal dimaksudkan untuk menjadi single threaded secara efektif , tetapi ini tidak berarti bahwa itu tidak dapat diartikan berbeda.

Katakanlah Anda memiliki kode berikut ...

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

Ini ditulis dengan harapan bahwa pada akhir loop, daftar harus memiliki 10.000 entri yang merupakan indeks kuadrat, tetapi VM dapat memperhatikan bahwa setiap iterasi loop tidak mempengaruhi yang lain, dan menafsirkan kembali menggunakan dua utas.

Utas pertama

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

Utas kedua

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

Saya menyederhanakan di sini, karena array JS lebih rumit daripada potongan memori yang bodoh, tetapi jika kedua skrip ini dapat menambahkan entri ke array dengan cara yang aman, maka pada saat keduanya selesai dieksekusi, ia akan memiliki hasil yang sama dengan versi single-threaded.

Meskipun saya tidak mengetahui adanya VM yang mendeteksi kode yang dapat diparalelkan seperti ini, sepertinya VM akan muncul di masa depan untuk VM JIT, karena dapat menawarkan kecepatan lebih dalam beberapa situasi.

Dengan mengambil konsep ini lebih jauh, ada kemungkinan bahwa kode dapat diberi catatan untuk membiarkan VM tahu apa yang harus dikonversi menjadi kode multi-threaded.

// 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;
}

Karena Pekerja Web datang ke Javascript, tidak mungkin bahwa ini ... sistem yang lebih buruk akan pernah ada, tapi saya pikir aman untuk mengatakan Javascript adalah single-threaded oleh tradisi.

maks
sumber
2
Sebagian besar definisi bahasa dirancang untuk menjadi single-threaded secara efektif, dan menyatakan bahwa multithreading diizinkan selama efeknya identik. (mis. UML)
jevon
1
Saya harus setuju dengan jawabannya hanya karena ECMAScript saat ini tidak membuat ketentuan (walaupun saya pikir hal yang sama dapat dikatakan untuk C) untuk konteks eksekusi ECMAScript bersamaan . Kemudian, seperti jawaban ini, saya berpendapat bahwa setiap implementasi yang memiliki utas bersamaan dapat mengubah keadaan bersama, adalah ekstensi skrip ECMAS .
user2864740
3

Yah, Chrome adalah multi-proses, dan saya pikir setiap proses berurusan dengan kode Javascript sendiri, tetapi sejauh kode tahu, itu "single-threaded".

Tidak ada dukungan apa pun dalam Javascript untuk multi-threading, setidaknya tidak secara eksplisit, sehingga tidak ada bedanya.

Francisco Soto
sumber
2

Saya sudah mencoba contoh @ bobince dengan sedikit modifikasi:

<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>

Jadi, ketika Anda menekan Run, tutup sembulan peringatan dan lakukan "utas tunggal", Anda akan melihat sesuatu seperti ini:

click begin
click end
result = 20, should be 20

Tetapi jika Anda mencoba menjalankan ini di Opera atau Firefox stable di Windows dan meminimalkan / memaksimalkan jendela dengan popup peringatan di layar, maka akan ada sesuatu seperti ini:

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

Saya tidak ingin mengatakan, bahwa ini adalah "multithreading", tetapi beberapa kode telah dieksekusi pada waktu yang salah dengan saya tidak mengharapkan ini, dan sekarang saya memiliki keadaan rusak. Dan lebih baik tahu tentang perilaku ini.

Anton Alexandrenok
sumber
-4

Cobalah untuk menyarangkan dua fungsi setTimeout di dalam satu sama lain dan mereka akan berperilaku multithreaded (yaitu; timer luar tidak akan menunggu yang bagian dalam untuk menyelesaikan sebelum menjalankan fungsinya).

James
sumber
5
chrome melakukan ini dengan cara yang benar, tidak tahu di mana @James melihatnya sebagai multithreaded ...: setTimeout(function(){setTimeout(function(){console.log('i herd you liek async')}, 0); alert('yo dawg!')}, 0)(sebagai catatan, yo dawg harus SELALU datang dulu, kemudian output log konsol)
Tor Valamo