Izinkan saya mengatakan bahwa saya memiliki cukup banyak pengalaman Java, tetapi baru-baru ini menjadi tertarik pada bahasa fungsional. Baru-baru ini saya mulai melihat Scala, yang sepertinya merupakan bahasa yang sangat bagus.
Namun, saya telah membaca tentang kerangka Aktor Scala dalam Pemrograman di Scala , dan ada satu hal yang tidak saya mengerti. Dalam bab 30.4 disebutkan bahwa menggunakan react
daripada receive
memungkinkan untuk menggunakan kembali utas, yang bagus untuk kinerja, karena utas mahal di JVM.
Apakah ini berarti, selama saya ingat untuk menelepon, react
bukan receive
, saya dapat memulai sebanyak mungkin Aktor yang saya suka? Sebelum menemukan Scala, saya telah bermain dengan Erlang, dan penulis Programming Erlang membanggakan tentang pemijahan lebih dari 200.000 proses tanpa mengeluarkan keringat. Saya benci melakukan itu dengan utas Java. Batasan apa yang saya lihat di Scala dibandingkan dengan Erlang (dan Java)?
Juga, bagaimana utas ini digunakan kembali bekerja di Scala? Mari kita asumsikan, untuk kesederhanaan, bahwa saya hanya memiliki satu utas. Akankah semua aktor yang saya mulai berjalan secara berurutan di utas ini, atau apakah akan terjadi semacam peralihan tugas? Misalnya, jika saya memulai dua aktor yang mengirim pesan ping-pong satu sama lain, apakah saya akan mengambil risiko kebuntuan jika mereka dimulai di utas yang sama?
Menurut Programming in Scala , penggunaan aktor menulis react
lebih sulit daripada dengan receive
. Ini terdengar masuk akal, karena react
tidak kembali. Namun, buku ini selanjutnya menunjukkan bagaimana Anda dapat membuat react
loop di dalam menggunakan Actor.loop
. Hasilnya, Anda mendapatkan
loop {
react {
...
}
}
yang, bagi saya, tampak sangat mirip
while (true) {
receive {
...
}
}
yang digunakan sebelumnya dalam buku ini. Namun, buku itu mengatakan bahwa "dalam praktiknya, program membutuhkan setidaknya beberapa receive
". Jadi apa yang saya lewatkan di sini? Apa yang receive
tidak bisa dilakukan yang react
tidak bisa, selain pengembalian? Dan mengapa saya peduli?
Akhirnya, sampai pada inti dari apa yang saya tidak mengerti: buku terus menyebutkan bagaimana penggunaan react
memungkinkan untuk membuang tumpukan panggilan untuk menggunakan kembali utas. Bagaimana cara kerjanya? Mengapa perlu membuang stack panggilan? Dan mengapa stack panggilan bisa dibuang ketika sebuah fungsi diakhiri dengan melemparkan pengecualian ( react
), tetapi tidak ketika itu diakhiri dengan mengembalikan ( receive
)?
Saya mendapat kesan bahwa Programming in Scala telah mengabaikan beberapa masalah utama di sini, yang memalukan, karena jika tidak, ini adalah buku yang sangat bagus.
sumber
Jawaban:
Pertama, setiap aktor yang menunggu
receive
sedang menempati utas. Jika tidak pernah menerima apa pun, utas itu tidak akan pernah melakukan apa pun. Aktor direact
tidak menempati utas apa pun hingga ia menerima sesuatu. Setelah menerima sesuatu, utas dialokasikan untuk itu, dan diinisialisasi di dalamnya.Sekarang, bagian inisialisasi itu penting. Utas penerima diharapkan mengembalikan sesuatu, utas yang bereaksi tidak. Jadi status tumpukan sebelumnya di akhir yang terakhir
react
dapat, dan, sepenuhnya dibuang. Tidak perlu menyimpan atau memulihkan status tumpukan membuat utas lebih cepat untuk memulai.Ada berbagai alasan kinerja mengapa Anda mungkin menginginkan satu atau lainnya. Seperti yang Anda ketahui, memiliki terlalu banyak utas di Java bukanlah ide yang baik. Di sisi lain, karena Anda harus melampirkan aktor ke utas sebelum itu bisa
react
, itu lebih cepat untukreceive
pesan daripadareact
itu. Jadi, jika Anda memiliki aktor yang menerima banyak pesan tetapi tidak melakukan banyak hal dengannya, penundaan tambahanreact
mungkin membuatnya terlalu lambat untuk tujuan Anda.sumber
Jawabannya adalah "ya" - jika aktor Anda tidak memblokir apa pun di kode Anda dan Anda sedang menggunakannya
react
, maka Anda dapat menjalankan program "bersamaan" dalam satu utas (coba setel properti sistemactors.maxPoolSize
untuk mengetahuinya).Salah satu alasan yang lebih jelas mengapa perlu membuang stack panggilan adalah bahwa jika tidak,
loop
metode akan diakhiri dengan aStackOverflowError
. Karena itu, kerangka kerja agak cerdik mengakhiri areact
dengan melemparSuspendActorException
, yang ditangkap oleh kode perulangan yang kemudian menjalankanreact
lagi melaluiandThen
metode.Lihatlah
mkBody
metode dalamActor
dan kemudianseq
metode untuk melihat bagaimana loop menjadwal ulang sendiri - hal yang sangat pintar!sumber
Pernyataan tentang "membuang tumpukan" itu juga membingungkan saya untuk sementara waktu dan saya rasa saya mengerti sekarang dan inilah pemahaman saya sekarang. Dalam kasus "menerima" ada thread khusus yang memblokir pesan (menggunakan object.wait () pada monitor) dan ini berarti bahwa tumpukan thread lengkap tersedia dan siap untuk melanjutkan dari titik "menunggu" saat menerima pesan. Misalnya jika Anda memiliki kode berikut
utas akan menunggu dalam panggilan terima sampai pesan diterima dan kemudian akan melanjutkan dan mencetak pesan "setelah menerima dan mencetak 10" dan dengan nilai "10" yang ada di bingkai tumpukan sebelum utas diblokir.
Dalam kasus react, tidak ada thread khusus seperti itu, seluruh tubuh metode dari metode react ditangkap sebagai penutupan dan dieksekusi oleh beberapa thread sembarang pada aktor terkait yang menerima pesan. Ini berarti hanya pernyataan yang dapat ditangkap sebagai penutupan saja yang akan dieksekusi dan di sanalah jenis kembalian "Nothing" ikut bermain. Perhatikan kode berikut
Jika react memiliki tipe kembalian dari kekosongan, itu berarti legal untuk memiliki pernyataan setelah panggilan "react" (dalam contoh pernyataan println yang mencetak pesan "setelah bereaksi dan mencetak 10"), tetapi pada kenyataannya itu tidak akan pernah dieksekusi karena hanya isi dari metode "react" yang ditangkap dan diurutkan untuk dieksekusi nanti (pada saat kedatangan pesan). Karena kontrak react memiliki tipe kembalian "Nothing", maka tidak ada pernyataan yang mengikuti react, dan karenanya tidak ada alasan untuk mempertahankan stack. Dalam contoh di atas, variabel "a" tidak harus dipertahankan karena pernyataan setelah panggilan react tidak dijalankan sama sekali. Perhatikan bahwa semua variabel yang dibutuhkan oleh tubuh react sudah ditangkap sebagai closure, sehingga bisa dieksekusi dengan baik.
Framework aktor java Kilim sebenarnya melakukan pemeliharaan tumpukan dengan menyimpan tumpukan yang dibuka pada saat reaksi menerima pesan.
sumber
+a
di cuplikan kode, bukan+10
?Hanya untuk memilikinya di sini:
Pemrograman Berbasis Acara tanpa Pembalikan Kontrol
Makalah ini ditautkan dari scala api untuk Aktor dan memberikan kerangka teoritis untuk implementasi aktor. Ini termasuk mengapa bereaksi mungkin tidak pernah kembali.
sumber
Saya belum melakukan pekerjaan besar dengan scala / akka, namun saya mengerti bahwa ada perbedaan yang sangat signifikan dalam cara aktor dijadwalkan. Akka hanyalah sebuah threadpool cerdas yang mana waktu pemotongan eksekusi aktor ... Setiap saat slice akan menjadi salah satu eksekusi pesan sampai selesai oleh aktor tidak seperti di Erlang yang bisa sesuai instruksi ?!
Hal ini membuat saya berpikir bahwa bereaksi lebih baik karena mengisyaratkan utas saat ini untuk mempertimbangkan aktor lain untuk penjadwalan di mana saat menerima "mungkin" melibatkan utas saat ini untuk terus mengeksekusi pesan lain untuk aktor yang sama.
sumber