Aktor scala: menerima vs bereaksi


110

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 reactdaripada receivememungkinkan untuk menggunakan kembali utas, yang bagus untuk kinerja, karena utas mahal di JVM.

Apakah ini berarti, selama saya ingat untuk menelepon, reactbukan 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 reactlebih sulit daripada dengan receive. Ini terdengar masuk akal, karena reacttidak kembali. Namun, buku ini selanjutnya menunjukkan bagaimana Anda dapat membuat reactloop 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 receivetidak bisa dilakukan yang reacttidak bisa, selain pengembalian? Dan mengapa saya peduli?

Akhirnya, sampai pada inti dari apa yang saya tidak mengerti: buku terus menyebutkan bagaimana penggunaan reactmemungkinkan 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.


Jawaban:


78

Pertama, setiap aktor yang menunggu receivesedang menempati utas. Jika tidak pernah menerima apa pun, utas itu tidak akan pernah melakukan apa pun. Aktor di reacttidak 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 reactdapat, 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 untuk receivepesan daripada reactitu. Jadi, jika Anda memiliki aktor yang menerima banyak pesan tetapi tidak melakukan banyak hal dengannya, penundaan tambahan reactmungkin membuatnya terlalu lambat untuk tujuan Anda.


21

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 sistem actors.maxPoolSizeuntuk mengetahuinya).

Salah satu alasan yang lebih jelas mengapa perlu membuang stack panggilan adalah bahwa jika tidak, loopmetode akan diakhiri dengan a StackOverflowError. Karena itu, kerangka kerja agak cerdik mengakhiri a reactdengan melempar SuspendActorException, yang ditangkap oleh kode perulangan yang kemudian menjalankan reactlagi melalui andThenmetode.

Lihatlah mkBodymetode dalam Actordan kemudian seqmetode untuk melihat bagaimana loop menjadwal ulang sendiri - hal yang sangat pintar!


20

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

  def a = 10;
  while (! done)  {
     receive {
        case msg =>  println("MESSAGE RECEIVED: " + msg)
     }
     println("after receive and printing a " + a)
  }

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

  def a = 10;
  while (! done)  {
     react {
        case msg =>  println("MESSAGE RECEIVED: " + msg)
     }
     println("after react and printing a " + a) 
  }

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.


Terima kasih, itu sangat informatif. Tapi bukankah yang Anda maksud +adi cuplikan kode, bukan +10?
jqno

Jawaban yang bagus. Aku juga tidak mengerti.
santiagobasulto


0

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.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.