นักแสดง Scala: รับและตอบสนอง


110

ขอบอกก่อนว่าฉันมีประสบการณ์ Java ค่อนข้างมาก แต่เพิ่งเริ่มสนใจภาษาที่ใช้งานได้ไม่นาน เมื่อเร็ว ๆ นี้ฉันได้เริ่มดู Scala ซึ่งดูเหมือนเป็นภาษาที่ดีมาก

อย่างไรก็ตามฉันได้อ่านเกี่ยวกับกรอบนักแสดงของ Scala ในProgramming ใน Scalaและมีสิ่งหนึ่งที่ฉันไม่เข้าใจ ในบทที่ 30.4 กล่าวว่าการใช้reactแทนreceiveทำให้สามารถใช้เธรดซ้ำได้ซึ่งดีต่อประสิทธิภาพเนื่องจากเธรดมีราคาแพงใน JVM

นี่หมายความว่าตราบใดที่ฉันจำได้ว่าต้องเรียกreactแทนreceiveฉันสามารถเริ่มนักแสดงมากเท่าที่ฉันชอบได้หรือไม่? ก่อนที่จะค้นพบ Scala ฉันเคยเล่นกับ Erlang และผู้เขียนProgramming Erlangภูมิใจนำเสนอเกี่ยวกับการวางไข่กว่า 200,000 กระบวนการโดยไม่ต้องเสียเหงื่อ ฉันไม่อยากทำแบบนั้นกับเธรด Java ฉันกำลังดูข้อ จำกัด ประเภทใดใน Scala เมื่อเทียบกับ Erlang (และ Java)

นอกจากนี้เธรดนี้จะใช้ซ้ำใน Scala ได้อย่างไร สมมติเพื่อความง่ายผมมีด้ายเพียงเส้นเดียว นักแสดงทุกคนที่ฉันเริ่มทำงานตามลำดับในเธรดนี้หรือการสลับงานบางอย่างจะเกิดขึ้น? ตัวอย่างเช่นหากฉันเริ่มต้นนักแสดงสองคนที่ส่งข้อความปิงปองถึงกันฉันจะเสี่ยงต่อการชะงักงันหรือไม่หากพวกเขาเริ่มต้นด้วยเธรดเดียวกัน

ตามที่การเขียนโปรแกรมใน Scalaเขียนนักแสดงที่จะใช้งานยากกว่าด้วยreact receiveฟังดูมีความเป็นไปได้เนื่องจากreactไม่กลับมา แต่หนังสือเล่มนี้ไปในการแสดงให้เห็นว่าคุณสามารถใส่ภายในห่วงใช้react Actor.loopเป็นผลให้คุณได้รับ

loop {
    react {
        ...
    }
}

ซึ่งสำหรับฉันดูเหมือนจะค่อนข้างคล้ายกับ

while (true) {
    receive {
        ...
    }
}

ซึ่งใช้ก่อนหน้านี้ในหนังสือเล่มนี้ ถึงกระนั้นหนังสือเล่มนี้ยังบอกว่า "ในทางปฏิบัติโปรแกรมจะต้องมีอย่างน้อยสองสามโปรแกรมreceive" แล้วฉันพลาดอะไรที่นี่? สิ่งที่receiveทำreactไม่ได้นอกจากกลับมา แล้วฉันจะแคร์ทำไม?

ในที่สุดก็มาถึงหัวใจหลักของสิ่งที่ฉันไม่เข้าใจ: หนังสือเล่มนี้ยังคงกล่าวถึงวิธีการใช้ที่reactทำให้สามารถทิ้ง call stack เพื่อใช้เธรดซ้ำได้ ทำงานอย่างไร? เหตุใดจึงจำเป็นต้องทิ้ง call stack? และเหตุใด call stack จึงสามารถละทิ้งได้เมื่อฟังก์ชันสิ้นสุดโดยการทิ้งข้อยกเว้น ( react) แต่ไม่ใช่เมื่อสิ้นสุดโดยการส่งคืน ( receive)

ฉันรู้สึกว่าการเขียนโปรแกรมใน Scalaได้รับการแก้ไขปัญหาสำคัญบางประการที่นี่ซึ่งเป็นเรื่องที่น่าเสียดายเพราะอย่างอื่นมันเป็นหนังสือที่ยอดเยี่ยมอย่างแท้จริง


คำตอบ:


78

ครั้งแรกที่นักแสดงแต่ละคนรอในreceiveครอบครองด้าย ถ้ามันไม่เคยได้รับอะไรเลยเธรดนั้นจะไม่ทำอะไรเลย นักแสดงreactจะไม่ครอบครองด้ายใด ๆ จนกว่าจะได้รับบางสิ่งบางอย่าง เมื่อได้รับบางสิ่งบางอย่างเธรดจะถูกจัดสรรให้และจะเริ่มต้นในนั้น

ตอนนี้ส่วนเริ่มต้นมีความสำคัญ เธรดที่ได้รับคาดว่าจะส่งคืนบางสิ่งเธรดที่ตอบสนองไม่ได้ ดังนั้นสถานะสแต็กก่อนหน้าในตอนท้ายของครั้งสุดท้ายreactสามารถเป็นได้และถูกทิ้งทั้งหมด ไม่จำเป็นต้องบันทึกหรือกู้คืนสถานะสแต็กทำให้เธรดเริ่มต้นได้เร็วขึ้น

มีเหตุผลด้านประสิทธิภาพหลายประการที่คุณอาจต้องการอย่างใดอย่างหนึ่ง ดังที่คุณทราบการมีเธรดมากเกินไปใน Java ไม่ใช่ความคิดที่ดี บนมืออื่น ๆ เพราะคุณมีการแนบนักแสดงไปด้ายก่อนที่มันจะสามารถreactก็เป็นได้เร็วขึ้นเพื่อreceiveข้อความกว่าreactมัน ดังนั้นหากคุณมีนักแสดงที่ได้รับข้อความจำนวนมาก แต่ได้รับข้อความนั้นน้อยมากความล่าช้าที่เพิ่มขึ้นreactอาจทำให้ช้าเกินไปสำหรับวัตถุประสงค์ของคุณ


21

คำตอบคือ "ใช่" - หากนักแสดงของคุณไม่ได้ปิดกั้นสิ่งใด ๆ ในโค้ดของคุณและคุณกำลังใช้reactงานอยู่คุณสามารถรันโปรแกรม"ทำงานพร้อมกัน" ได้ภายในเธรดเดียว (ลองตั้งค่าคุณสมบัติของระบบactors.maxPoolSizeเพื่อค้นหา)

อีกเหตุผลหนึ่งที่ชัดเจนว่าทำไมจึงจำเป็นต้องทิ้ง call stackคือไม่เช่นนั้นloopวิธีการนี้จะจบลงด้วยStackOverflowErrorไฟล์. ตามที่เป็นอยู่เฟรมเวิร์กจะจบลงอย่างชาญฉลาดreactด้วยการโยน a SuspendActorExceptionซึ่งถูกจับโดยรหัสการวนซ้ำซึ่งจะเรียกใช้reactอีกครั้งผ่านandThenวิธีการ

ดูmkBodyวิธีการในActorแล้วseqวิธีการเพื่อดูว่าการวนซ้ำจัดตารางเวลาตัวเองอย่างไร - สิ่งที่ฉลาดมาก!


20

ข้อความ "ทิ้งสแต็ก" ทำให้ฉันสับสนไปชั่วขณะและฉันคิดว่าฉันเข้าใจแล้วและนี่คือความเข้าใจของฉันในตอนนี้ ในกรณีของ "รับ" มีการบล็อกเธรดเฉพาะบนข้อความ (โดยใช้ object.wait () บนจอภาพ) ซึ่งหมายความว่าเธรดสแต็กที่สมบูรณ์พร้อมใช้งานและพร้อมที่จะดำเนินการต่อจากจุด "รอ" เมื่อได้รับ ข้อความ. ตัวอย่างเช่นหากคุณมีรหัสต่อไปนี้

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

เธรดจะรอในสายรับจนกว่าจะได้รับข้อความจากนั้นจะดำเนินการต่อและพิมพ์ข้อความ "หลังจากรับและพิมพ์ 10" และมีค่า "10" ซึ่งอยู่ในกรอบสแต็กก่อนที่เธรดจะถูกบล็อก

ในกรณีของการตอบสนองไม่มีเธรดเฉพาะดังกล่าวเนื้อหาของวิธีการทั้งหมดของวิธีการตอบสนองจะถูกจับเป็นการปิดและดำเนินการโดยเธรดตามอำเภอใจบางส่วนที่นักแสดงที่เกี่ยวข้องได้รับข้อความ ซึ่งหมายความว่าเฉพาะคำสั่งที่จับได้ว่าเป็นการปิดเพียงอย่างเดียวเท่านั้นที่จะถูกดำเนินการและนั่นคือสิ่งที่ส่งคืนประเภท "Nothing" มาเล่น พิจารณารหัสต่อไปนี้

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

ถ้า react มีประเภท return เป็น void หมายความว่าถูกต้องตามกฎหมายที่จะมีข้อความหลังการเรียก "react" (ในตัวอย่างคำสั่ง println ที่พิมพ์ข้อความ "after react and printing a 10") แต่ในความเป็นจริงแล้ว จะไม่ถูกประหารชีวิตเนื่องจากมีเพียงเนื้อความของวิธีการ "ตอบสนอง" เท่านั้นที่ถูกจับและจัดลำดับสำหรับการดำเนินการในภายหลัง (เมื่อข้อความมาถึง) เนื่องจากสัญญาการตอบสนองมีการส่งคืนประเภท "ไม่มีอะไร" จึงไม่สามารถมีข้อความใด ๆ ที่เกิดขึ้นตามมาได้และไม่มีเหตุผลใดที่จะรักษาสแต็ก ในตัวอย่างด้านบนตัวแปร "a" จะไม่ต้องคงไว้เนื่องจากคำสั่งหลังจากการเรียกใช้ react จะไม่ดำเนินการเลย โปรดทราบว่าตัวแปรที่จำเป็นทั้งหมดโดย body of react ถูกจับเป็นปิดอยู่แล้วดังนั้นจึงสามารถดำเนินการได้ดี

เฟรมเวิร์กนักแสดง java Kilimทำการบำรุงรักษาสแต็กโดยการบันทึกสแต็กที่ไม่ได้รับการควบคุมเมื่อตอบสนองในการรับข้อความ


ขอบคุณที่ให้ข้อมูลมาก แต่คุณไม่ได้หมายถึง+aข้อมูลโค้ดแทน+10หรือ?
jqno

คำตอบที่ดี ฉันไม่เข้าใจเช่นนั้น
santiagobasulto

8

เพียงแค่มีที่นี่:

การเขียนโปรแกรมตามเหตุการณ์โดยไม่ต้องผกผันการควบคุม

เอกสารเหล่านี้เชื่อมโยงจากสกาลา API สำหรับนักแสดงและให้กรอบทางทฤษฎีสำหรับการนำไปใช้กับนักแสดง ซึ่งรวมถึงเหตุใดจึงไม่สามารถตอบสนองได้


และกระดาษแผ่นที่สอง การควบคุมสแปมที่ไม่ดี ... :( [นักแสดงที่รวมเธรดและเหตุการณ์ที่ตรงกัน ] [2] [2]: lamp.epfl.ch/~phaller/doc/haller07coord.pdf "นักแสดงที่รวมเธรดและกิจกรรม"
Hexren

0

ฉันยังไม่ได้ทำงานหลักกับ scala / akka แต่ฉันเข้าใจว่ามีความแตกต่างอย่างมากในวิธีกำหนดเวลาของนักแสดง Akka เป็นเพียงเธรดพูลที่ชาญฉลาดซึ่งเป็นการแบ่งเวลาการดำเนินการของนักแสดง ... ทุกครั้งที่ slice จะเป็นการดำเนินการข้อความหนึ่งครั้งเพื่อให้นักแสดงไม่เหมือนใน Erlang ซึ่งอาจเป็นไปตามคำสั่ง?!

สิ่งนี้ทำให้ฉันคิดว่าการตอบสนองนั้นดีกว่าเนื่องจากเป็นการบอกใบ้ให้กับเธรดปัจจุบันเพื่อพิจารณาตัวแสดงอื่น ๆ สำหรับการตั้งเวลาโดยที่รับ "อาจ" เข้าร่วมเธรดปัจจุบันเพื่อดำเนินการต่อข้อความอื่นสำหรับนักแสดงคนเดียวกัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.