Diễn viên Scala: nhận và phản ứng


110

Đầu tiên tôi xin nói rằng tôi có khá nhiều kinh nghiệm về Java, nhưng chỉ gần đây mới quan tâm đến các ngôn ngữ chức năng. Gần đây tôi đã bắt đầu xem xét Scala, nó có vẻ như là một ngôn ngữ rất hay.

Tuy nhiên, tôi đã đọc về khung Diễn viên của Scala trong Lập trình trong Scala , và có một điều tôi không hiểu. Trong chương 30.4, nó nói rằng sử dụng reactthay vì receivelàm cho nó có thể sử dụng lại các luồng, điều này tốt cho hiệu suất, vì các luồng rất đắt trong JVM.

Điều này có nghĩa là, miễn là tôi nhớ gọi reactthay vì receivetôi có thể bắt đầu bao nhiêu Diễn viên tùy thích? Trước khi khám phá ra Scala, tôi đã chơi với Erlang và tác giả của Lập trình Erlang tự hào về việc tạo ra hơn 200.000 quy trình mà không tốn một giọt mồ hôi. Tôi không muốn làm điều đó với các luồng Java. Tôi đang xem những giới hạn nào trong Scala so với Erlang (và Java)?

Ngoài ra, việc tái sử dụng luồng này hoạt động như thế nào trong Scala? Hãy giả sử, vì đơn giản, tôi chỉ có một luồng. Liệu tất cả các tác nhân mà tôi bắt đầu chạy tuần tự trong chuỗi này, hay một số loại chuyển đổi tác vụ sẽ diễn ra? Ví dụ: nếu tôi bắt đầu hai diễn viên bóng bàn nhắn tin cho nhau, liệu tôi có gặp nguy cơ bế tắc nếu họ bắt đầu trong cùng một chủ đề không?

Theo Lập trình trong Scala , viết các diễn viên để sử dụng reactkhó hơn với receive. Điều này nghe có vẻ hợp lý, vì reactnó không quay trở lại. Tuy nhiên, cuốn sách tiếp tục chỉ ra cách bạn có thể đặt reactmột vòng lặp bên trong bằng cách sử dụng Actor.loop. Kết quả là, bạn nhận được

loop {
    react {
        ...
    }
}

mà đối với tôi, có vẻ khá giống với

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

được sử dụng trước đó trong cuốn sách. Tuy nhiên, cuốn sách nói rằng "trong thực tế, các chương trình sẽ cần ít nhất một vài receive". Vậy tôi còn thiếu gì ở đây? Điều gì có thể receivelàm mà reactkhông thể, ngoài việc trở lại? Và tại sao tôi lại quan tâm?

Cuối cùng, đi đến cốt lõi của những gì tôi không hiểu: cuốn sách tiếp tục đề cập đến cách sử dụng reactlàm cho nó có thể loại bỏ ngăn xếp cuộc gọi để sử dụng lại chuỗi. Nó hoạt động như thế nào? Tại sao cần loại bỏ ngăn xếp cuộc gọi? Và tại sao ngăn xếp cuộc gọi có thể bị loại bỏ khi một hàm kết thúc bằng cách ném một ngoại lệ ( react), nhưng không phải khi nó kết thúc bằng cách trả về ( receive)?

Tôi có ấn tượng rằng Lập trình trong Scala đã đề cập đến một số vấn đề chính ở đây, điều này thật đáng tiếc, bởi vì nếu không thì đây là một cuốn sách thực sự xuất sắc.


Câu trả lời:


78

Đầu tiên, mỗi tác nhân đang chờ đợi receiveđang chiếm một luồng. Nếu nó không bao giờ nhận được bất cứ thứ gì, chuỗi đó sẽ không bao giờ làm được gì cả. Một tác nhân trên reactkhông chiếm bất kỳ luồng nào cho đến khi nó nhận được thứ gì đó. Khi nó nhận được thứ gì đó, một luồng sẽ được cấp phát cho nó và nó được khởi tạo trong đó.

Bây giờ, phần khởi tạo là quan trọng. Một luồng nhận được mong đợi sẽ trả về một cái gì đó, một luồng phản ứng thì không. Vì vậy, trạng thái ngăn xếp trước đó ở cuối cùng reactcó thể bị loại bỏ hoàn toàn. Không cần lưu hoặc khôi phục trạng thái ngăn xếp giúp luồng bắt đầu nhanh hơn.

Có nhiều lý do hiệu suất khác nhau khiến bạn có thể muốn cái này hoặc cái khác. Như bạn đã biết, có quá nhiều luồng trong Java không phải là một ý kiến ​​hay. Mặt khác, bởi vì bạn phải gắn một tác nhân vào một chuỗi trước khi nó có thể react, nó sẽ nhanh hơn đến receivemột thông điệp so reactvới nó. Vì vậy, nếu bạn có các tác nhân nhận được nhiều tin nhắn nhưng thực hiện rất ít với nó, thì sự chậm trễ bổ sung reactcó thể làm cho nó quá chậm so với mục đích của bạn.


21

Câu trả lời là "có" - nếu các tác nhân của bạn không chặn bất kỳ thứ gì trong mã của bạn và bạn đang sử dụng react, thì bạn có thể chạy chương trình "đồng thời" của mình trong một chuỗi duy nhất (hãy thử đặt thuộc tính hệ thống actors.maxPoolSizeđể tìm hiểu).

Một trong những lý do rõ ràng hơn tại sao cần phải loại bỏ ngăn xếp cuộc gọi là vì nếu không loopphương thức sẽ kết thúc bằng a StackOverflowError. Như vậy, khung công tác khá khéo léo kết thúc a reactbằng cách ném a SuspendActorException, mã này bị bắt bởi mã lặp, sau đó chạy reactlại thông qua andThenphương thức.

Hãy xem mkBodyphương thức trong Actorvà sau đó là seqphương thức để xem vòng lặp tự lập lại lịch trình như thế nào - thật là thông minh!


20

Những câu nói "vứt bỏ chồng chất" đó cũng khiến tôi bối rối trong một thời gian và tôi nghĩ bây giờ tôi đã hiểu được và đây là sự hiểu biết của tôi. Trong trường hợp "nhận" có một luồng chuyên dụng chặn trên thư (sử dụng object.wait () trên màn hình) và điều này có nghĩa là ngăn xếp luồng hoàn chỉnh đã có sẵn và sẵn sàng tiếp tục từ điểm "chờ" khi nhận được thông điệp. Ví dụ: nếu bạn có mã sau

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

chuỗi sẽ đợi trong cuộc gọi nhận cho đến khi nhận được tin nhắn và sau đó sẽ tiếp tục và in thông báo "sau khi nhận và in thông báo 10" và với giá trị "10" nằm trong khung ngăn xếp trước khi luồng bị chặn.

Trong trường hợp phản ứng không có luồng chuyên dụng như vậy, toàn bộ phần thân phương thức của phương thức phản ứng được ghi lại như một bao đóng và được thực thi bởi một số luồng tùy ý trên tác nhân tương ứng nhận một thông báo. Điều này có nghĩa là chỉ những câu lệnh có thể được nắm bắt dưới dạng một lệnh đóng mới được thực thi và đó là nơi loại trả về "Không có gì" xuất hiện. Hãy xem xét đoạn mã sau

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

Nếu react có kiểu trả về là void, điều đó có nghĩa là hợp pháp khi có các câu lệnh sau lệnh gọi "react" (trong ví dụ câu lệnh println in ra thông báo "sau khi react và in ra 10"), nhưng trên thực tế thì sẽ không bao giờ được thực thi vì chỉ phần thân của phương thức "react" được ghi lại và sắp xếp để thực thi sau này (khi có thông báo). Vì hợp đồng của phản ứng có kiểu trả về là "Không có gì" nên không thể có bất kỳ câu lệnh nào theo sau phản ứng và không có lý do gì để duy trì ngăn xếp. Trong ví dụ trên, biến "a" sẽ không phải được duy trì vì các câu lệnh sau khi gọi phản ứng hoàn toàn không được thực hiện. Lưu ý rằng tất cả các biến cần thiết của phần thân của phản ứng đã được ghi lại dưới dạng bao đóng, vì vậy nó có thể thực thi tốt.

Khung tác nhân java Kilim thực sự thực hiện bảo trì ngăn xếp bằng cách lưu ngăn xếp được mở trên phản ứng nhận được thông báo.


Cảm ơn, đó là rất nhiều thông tin. Nhưng không phải ý bạn là +atrong các đoạn mã, thay vì +10?
jqno

Câu trả lời chính xác. Tôi cũng không hiểu.
santiagobasulto

8

Chỉ cần có nó ở đây:

Lập trình dựa trên sự kiện mà không cần đảo ngược kiểm soát

Các bài báo này được liên kết từ api scala cho Actor và cung cấp khung lý thuyết cho việc thực hiện agent. Điều này bao gồm lý do tại sao phản ứng có thể không bao giờ quay trở lại.


Và tờ giấy thứ hai. Kiểm soát spam kém ... :( [Tác nhân hợp nhất chủ đề và sự kiện] [2] [2]: lamp.epfl.ch/~phaller/doc/haller07coord.pdf " Tác nhân hợp nhất chuỗi và sự kiện"
Hexren

0

Tôi chưa thực hiện bất kỳ công việc lớn nào với scala / akka, tuy nhiên tôi hiểu rằng có một sự khác biệt rất lớn trong cách các diễn viên được lên lịch. Akka chỉ là một threadpool thông minh có chức năng cắt thời gian thực thi của các diễn viên ... Mỗi lát cắt sẽ là một lần thực thi thông điệp được hoàn thành bởi một diễn viên không giống như trong Erlang mà có thể là theo chỉ dẫn ?!

Điều này khiến tôi nghĩ rằng phản ứng tốt hơn vì nó gợi ý cho chuỗi hiện tại xem xét các tác nhân khác để lập lịch khi nhận "có thể" tham gia vào chuỗi hiện tại để tiếp tục thực thi các thông báo khác cho cùng một tác nhân.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.