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?
javascript
concurrency
Egor Pavlikhin
sumber
sumber
Jawaban:
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:
Menghasilkan
log in, blur, log out
semua kecuali IE. Peristiwa ini tidak hanya terjadi karena Anda meneleponfocus()
langsung, tetapi bisa juga terjadi karena Anda meneleponalert()
, atau membuka jendela sembul, atau apa pun yang menggerakkan fokus.Ini juga dapat menyebabkan acara lain. Sebagai contoh, tambahkan
i.onchange
pendengar dan ketik sesuatu di input sebelumfocus()
panggilan tidak fokus, dan urutan log adalahlog in, change, blur, log out
, kecuali di Opera di mana itulog in, blur, log out, change
dan IE di mana itu (bahkan kurang jelas)log in, change, log out, blur
.Demikian pula memanggil
click()
elemen yang menyediakannya memanggilonclick
penangan segera di semua browser (setidaknya ini konsisten!).(Saya menggunakan
on...
properti event handler langsung di sini, tetapi hal yang sama terjadi padaaddEventListener
danattachEvent
.)Ada juga banyak keadaan di mana peristiwa dapat memanas saat kode Anda masuk, meskipun Anda tidak melakukan apa pun untuk memprovokasi. Sebuah contoh:
Tekan
alert
dan 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 mendapatkanalert in, resize, alert out
di 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 sepertiscroll
) 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
showModalDialog
di 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
setInterval
fungsi. Jika Anda menulis kerangka kerja yang Anda maksudkan untuk digunakan oleh aplikasi yang kompleks, melakukan ini bisa menjadi langkah yang baik.postMessage
semoga juga akan meringankan rasa sakit scripting lintas dokumen di masa depan.sumber
blur
setelah kode Anda mengembalikan kontrol ke browser.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.)sumber
Ya, meskipun Anda masih dapat mengalami beberapa masalah pemrograman bersamaan (terutama kondisi ras) saat menggunakan salah satu API asinkron seperti setInterval dan callback xmlhttp.
sumber
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.
sumber
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:
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:
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 :
PS
Selain teori, selalu siap tentang kemungkinan kasus sudut dan bug yang dijelaskan pada jawaban yang diterima
sumber
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.
setTimeout
dansetInterval
merupakan contoh, atau setidaknya contoh pseudo, dari lingkungan host yang menyediakan cara untuk melakukan beberapa konkurensi (bahkan jika itu bukan konkurensi).sumber
Sebenarnya, jendela induk dapat berkomunikasi dengan jendela anak atau saudara kandung atau bingkai yang menjalankan utas eksekusi sendiri.
sumber
@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.
sumber
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 ...
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
Utas kedua
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.
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.
sumber
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.
sumber
Saya sudah mencoba contoh @ bobince dengan sedikit modifikasi:
Jadi, ketika Anda menekan Run, tutup sembulan peringatan dan lakukan "utas tunggal", Anda akan melihat sesuatu seperti ini:
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:
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.
sumber
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).
sumber
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)