2009-01-23 14 views
82

Tôi đã xem khả năng hiển thị ngày càng tăng của các ngôn ngữ lập trình chức năng và các tính năng trong một thời gian. Tôi nhìn vào họ và không thấy lý do của sự kháng cáo.Làm cách nào/tại sao ngôn ngữ chức năng (cụ thể là Erlang) có quy mô tốt?

Sau đó, gần đây tôi đã tham dự bài trình bày "Khái niệm cơ bản về Erlang" của Kevin Smith tại Codemash.

Tôi rất thích bài thuyết trình và đã học được rằng rất nhiều thuộc tính của lập trình chức năng giúp dễ dàng tránh các vấn đề về luồng/đồng thời. Tôi hiểu rằng việc thiếu nhà nước và khả năng biến đổi khiến cho nhiều chủ đề không thể thay đổi cùng một dữ liệu, nhưng Kevin nói (nếu tôi hiểu đúng) tất cả các giao tiếp diễn ra thông qua tin nhắn và các mesages được xử lý đồng bộ (một lần nữa tránh các vấn đề tương tranh).

Nhưng tôi đã đọc rằng Erlang được sử dụng trong các ứng dụng có khả năng mở rộng cao (toàn bộ lý do Ericsson đã tạo ra nó ngay từ đầu). Làm thế nào nó có thể xử lý hiệu quả hàng nghìn yêu cầu mỗi giây nếu mọi thứ được xử lý như một thông báo được xử lý đồng bộ? Đó không phải là lý do tại sao chúng tôi bắt đầu chuyển sang xử lý không đồng bộ - vì vậy chúng tôi có thể tận dụng lợi thế của việc chạy nhiều luồng hoạt động cùng một lúc và đạt được khả năng mở rộng? Nó có vẻ như kiến ​​trúc này, trong khi an toàn hơn, là một bước lùi về mặt khả năng mở rộng. Tôi đang thiếu gì?

Tôi hiểu những người sáng tạo của Erlang cố tình tránh hỗ trợ luồng để tránh các vấn đề đồng thời, nhưng tôi nghĩ rằng đa luồng là cần thiết để đạt được khả năng mở rộng.

Ngôn ngữ lập trình chức năng có thể vốn an toàn như thế nào nhưng vẫn có tỷ lệ?

+0

[Chưa được đề cập]: Máy ảo Erlang 'mất sự không đồng bộ đến một cấp độ khác. Bởi ma thuật voodoo (asm) nó cho phép các hoạt động đồng bộ hóa như socket: đọc để chặn mà không dừng một chuỗi os. Điều này cho phép bạn viết mã đồng bộ khi các ngôn ngữ khác sẽ buộc bạn vào tổ gọi async-callback. Nó dễ dàng hơn nhiều để viết một ứng dụng mở rộng quy mô với hình ảnh tâm trí của các dịch vụ vi mô luồng đơn VS giữ hình ảnh lớn trong tâm trí mỗi khi bạn tack một cái gì đó vào cơ sở mã. –

+0

@Vans S Thú vị. –

Trả lời

89

Một ngôn ngữ chức năng không (nói chung) dựa vào biến đổi một biến. Bởi vì điều này, chúng ta không phải bảo vệ "trạng thái chia sẻ" của một biến, bởi vì giá trị được cố định. Điều này sẽ tránh phần lớn các vòng nhảy mà các ngôn ngữ truyền thống phải trải qua để thực hiện một thuật toán trên các bộ xử lý hoặc máy móc.

Erlang mang nó xa hơn các ngôn ngữ chức năng truyền thống bằng cách nướng trong hệ thống thông báo cho phép mọi thứ hoạt động trên hệ thống dựa trên sự kiện, nơi một đoạn mã chỉ lo lắng về việc nhận tin nhắn và gửi tin nhắn chứ không phải lo lắng về bức tranh lớn hơn.

Điều này có nghĩa là lập trình viên (danh nghĩa) không quan tâm rằng thông báo sẽ được xử lý trên một bộ xử lý hoặc máy khác: chỉ cần gửi tin nhắn là đủ tốt để tiếp tục. Nếu nó quan tâm đến một phản hồi, nó sẽ chờ đợi nó như là một tin nhắn khác.

Kết quả cuối cùng của việc này là mỗi đoạn mã độc lập với mọi đoạn mã khác. Không có mã được chia sẻ, không có trạng thái chia sẻ và tất cả các tương tác đến từ một hệ thống thư có thể được phân phối trong nhiều phần cứng (hoặc không).

Tương phản điều này với hệ thống truyền thống: chúng tôi phải đặt các mutex và các ẩn dụ xung quanh các biến "được bảo vệ" và thực thi mã. Chúng tôi có ràng buộc chặt chẽ trong một cuộc gọi chức năng thông qua ngăn xếp (chờ đợi sự trở lại xảy ra). Tất cả điều này tạo ra tắc nghẽn mà ít gặp vấn đề trong một hệ thống chia sẻ không có gì giống như Erlang.

EDIT: Tôi cũng nên chỉ ra rằng Erlang không đồng bộ. Bạn gửi tin nhắn của bạn và có thể/một ngày nào đó một tin nhắn khác sẽ trở lại. Hay không.

Điểm của Spencer về việc thực hiện ngoài trật tự cũng rất quan trọng và được trả lời tốt.

+0

Tôi hiểu điều này, nhưng không thấy cách mô hình tin nhắn hiệu quả.Tôi đoán ngược lại. Đây là một mắt mở thực sự cho tôi. Không có thắc mắc ngôn ngữ lập trình chức năng đang nhận được rất nhiều sự chú ý. –

+3

Bạn nhận được rất nhiều đồng thời * tiềm năng * trong một hệ thống không được chia sẻ. Việc triển khai không tốt (ví dụ, thông điệp cao vượt qua) có thể phóng ngư lôi này, nhưng Erlang dường như làm đúng và giữ mọi thứ có trọng lượng nhẹ. – Godeke

+0

Điều quan trọng cần lưu ý là trong khi Erlang có ngữ nghĩa truyền thông thì nó có một bộ nhớ dùng chung, do đó, nó có ngữ nghĩa được mô tả nhưng nó không sao chép tất cả mọi thứ nếu không cần. –

6

Bạn có thể có một sự hiểu lầm về cách Erlang hoạt động. Thời gian chạy Erlang giảm thiểu chuyển đổi ngữ cảnh trên CPU, nhưng nếu có nhiều CPU có sẵn, thì tất cả đều được sử dụng để xử lý thông báo. Bạn không có "chủ đề" theo nghĩa bạn làm trong các ngôn ngữ khác, nhưng bạn có thể có nhiều thư đang được xử lý đồng thời.

67

Hệ thống xếp hàng tin nhắn rất tuyệt vì nó có hiệu quả tạo ra hiệu ứng "lửa và chờ-kết quả" là phần đồng bộ bạn đang đọc. Điều gì làm cho điều này cực kỳ tuyệt vời là nó có nghĩa là các dòng không cần phải được thực hiện tuần tự. Xét đoạn mã sau:

r = methodWithALotOfDiskProcessing(); 
x = r + 1; 
y = methodWithALotOfNetworkProcessing(); 
w = x * y 

cân nhắc trong chốc lát mà methodWithALotOfDiskProcessing() mất khoảng 2 giây để hoàn thành và methodWithALotOfNetworkProcessing rằng() mất khoảng 1 giây để hoàn thành. Trong một ngôn ngữ thủ tục, mã này sẽ mất khoảng 3 giây để chạy vì các dòng sẽ được thực thi tuần tự. Chúng tôi đang lãng phí thời gian chờ đợi một phương pháp để hoàn thành mà có thể chạy đồng thời với một phương pháp khác mà không phải cạnh tranh cho một tài nguyên duy nhất. Trong một dòng ngôn ngữ chức năng của mã không dictate khi xử lý sẽ cố gắng cho họ. Một ngôn ngữ chức năng sẽ thử một cái gì đó như sau:

Execute line 1 ... wait. 
Execute line 2 ... wait for r value. 
Execute line 3 ... wait. 
Execute line 4 ... wait for x and y value. 
Line 3 returned ... y value set, message line 4. 
Line 1 returned ... r value set, message line 2. 
Line 2 returned ... x value set, message line 4. 
Line 4 returned ... done. 

Làm thế nào là mát? Bằng cách tiếp tục với mã và chỉ chờ đợi khi cần thiết, chúng tôi đã giảm thời gian chờ xuống còn hai giây một cách tự động! : D Vì vậy, có, trong khi mã là đồng bộ nó có xu hướng có một ý nghĩa khác hơn là trong ngôn ngữ thủ tục.

EDIT:

Một khi bạn nắm bắt khái niệm này kết hợp với bài Godeke của nó thật dễ dàng để tưởng tượng như thế nào đơn giản nó trở nên tận dụng nhiều bộ xử lý, trang trại máy chủ, lưu trữ dữ liệu dư thừa và ai biết được điều gì khác.

+0

Tuyệt! Tôi hoàn toàn hiểu lầm cách thông điệp được xử lý. Cảm ơn, bài đăng của bạn sẽ giúp bạn. –

+1

Giải thích tuyệt vời! –

+0

"Một ngôn ngữ chức năng sẽ thử một cái gì đó như sau" - Tôi không chắc chắn về các ngôn ngữ chức năng khác, nhưng trong Erlang ví dụ sẽ làm việc chính xác như trong trường hợp ngôn ngữ thủ tục. Bạn * có thể thực hiện hai tác vụ đó song song với các quy trình sinh sản, cho phép chúng thực hiện hai nhiệm vụ một cách không đồng bộ và thu được kết quả của chúng ở cuối, nhưng nó không giống như "trong khi mã đồng bộ nó có ý nghĩa khác hơn trong thủ tục ngôn ngữ. " Xem thêm câu trả lời của Chris. – hcs42

-2

Trong ngôn ngữ thuần túy, thứ tự đánh giá không quan trọng - trong một ứng dụng hàm fn (arg1, .. argn), các đối số n có thể được đánh giá song song. Điều đó đảm bảo một mức độ cao của (tự động) song song.

Erlang sử dụng một quá trình modell nơi một quá trình có thể chạy trong cùng một máy ảo, hoặc trên một bộ xử lý khác nhau - không có cách nào để nói. Điều đó chỉ có thể bởi vì các thông điệp được sao chép giữa các quá trình, không có trạng thái được chia sẻ (có thể thay đổi). Đa nguyên bộ vi xử lý đi xa hơn rất nhiều luồng, vì các luồng phụ thuộc vào bộ nhớ chia sẻ, điều này chỉ có thể có 8 luồng chạy song song trên CPU 8 lõi, trong khi đa xử lý có thể mở rộng tới hàng nghìn tiến trình song song.

10

Điều quan trọng cho phép Erlang quy mô có liên quan đến đồng thời.

Một hệ điều hành cung cấp đồng thời bởi hai cơ chế:

  • hệ điều hành xử lý
  • hệ điều hành chủ đề

Processes không chia sẻ trạng thái - một quá trình có thể không sụp đổ khác bằng cách thiết kế .

Chủ đề chia sẻ trạng thái - một chuỗi có thể gặp sự cố khác theo thiết kế - đó là vấn đề của bạn.

Với Erlang - một quy trình hệ điều hành được máy ảo sử dụng và máy ảo cung cấp đồng thời cho chương trình Erlang không bằng cách sử dụng luồng hệ điều hành mà bằng cách cung cấp các quy trình Erlang - đó là Erlang thực hiện bộ đếm thời gian của riêng nó.

Quy trình Erlang này trao đổi với nhau bằng cách gửi tin nhắn (được xử lý bởi máy ảo Erlang chứ không phải hệ điều hành). Các quá trình Erlang gọi nhau bằng một quá trình ID (PID) trong đó có một địa chỉ ba phần <<N3.N2.N1>>:

  • quá trình không N1 trên
  • VM N2 trên
  • máy N3 vật lý

Hai quy trình trên cùng một máy ảo, trên các máy ảo khác nhau trên cùng một máy hoặc hai máy giao tiếp theo cùng một cách - do đó, tỷ lệ của bạn độc lập với số lượng máy vật lý mà bạn triển khai ứng dụng của mình (trong xấp xỉ đầu tiên).

Erlang chỉ là chủ đề an toàn theo nghĩa nhỏ - nó không có chủ đề. (Ngôn ngữ có nghĩa là, máy ảo SMP/đa lõi sử dụng một luồng hệ điều hành trên mỗi lõi).

15

Có khả năng bạn đang trộn đồng bộ với tuần tự.

Phần thân của hàm trong erlang đang được xử lý tuần tự. Vì vậy, những gì Spencer nói về "hiệu ứng tự động" này không đúng với erlang. Bạn có thể mô hình hành vi này với erlang mặc dù.

Ví dụ: bạn có thể sinh ra một quy trình tính toán số từ trong một dòng. Vì chúng ta có một vài dòng, chúng ta sinh ra một tiến trình như vậy cho mỗi dòng và nhận các câu trả lời để tính toán một số tiền từ nó.

Bằng cách đó, chúng tôi sinh ra các quy trình thực hiện các phép tính "nặng" (sử dụng các lõi bổ sung nếu có) và sau đó chúng tôi thu thập kết quả.

-module(countwords). 
-export([count_words_in_lines/1]). 

count_words_in_lines(Lines) -> 
    % For each line in lines run spawn_summarizer with the process id (pid) 
    % and a line to work on as arguments. 
    % This is a list comprehension and spawn_summarizer will return the pid 
    % of the process that was created. So the variable Pids will hold a list 
    % of process ids. 
    Pids = [spawn_summarizer(self(), Line) || Line <- Lines], 
    % For each pid receive the answer. This will happen in the same order in 
    % which the processes were created, because we saved [pid1, pid2, ...] in 
    % the variable Pids and now we consume this list. 
    Results = [receive_result(Pid) || Pid <- Pids], 
    % Sum up the results. 
    WordCount = lists:sum(Results), 
    io:format("We've got ~p words, Sir!~n", [WordCount]). 

spawn_summarizer(S, Line) -> 
    % Create a anonymous function and save it in the variable F. 
    F = fun() -> 
     % Split line into words. 
     ListOfWords = string:tokens(Line, " "), 
     Length = length(ListOfWords), 
     io:format("process ~p calculated ~p words~n", [self(), Length]), 
     % Send a tuple containing our pid and Length to S. 
     S ! {self(), Length} 
    end, 
    % There is no return in erlang, instead the last value in a function is 
    % returned implicitly. 
    % Spawn the anonymous function and return the pid of the new process. 
    spawn(F). 

% The Variable Pid gets bound in the function head. 
% In erlang, you can only assign to a variable once. 
receive_result(Pid) -> 
    receive 
     % Pattern-matching: the block behind "->" will execute only if we receive 
     % a tuple that matches the one below. The variable Pid is already bound, 
     % so we are waiting here for the answer of a specific process. 
     % N is unbound so we accept any value. 
     {Pid, N} -> 
      io:format("Received \"~p\" from process ~p~n", [N, Pid]), 
      N 
    end. 

Và đây là những gì có vẻ như, khi chúng tôi chạy này trong vỏ: thông điệp

Eshell V5.6.5 (abort with ^G) 
1> Lines = ["This is a string of text", "and this is another", "and yet another", "it's getting boring now"]. 
["This is a string of text","and this is another", 
"and yet another","it's getting boring now"] 
2> c(countwords). 
{ok,countwords} 
3> countwords:count_words_in_lines(Lines). 
process <0.39.0> calculated 6 words 
process <0.40.0> calculated 4 words 
process <0.41.0> calculated 3 words 
process <0.42.0> calculated 4 words 
Received "6" from process <0.39.0> 
Received "4" from process <0.40.0> 
Received "3" from process <0.41.0> 
Received "4" from process <0.42.0> 
We've got 17 words, Sir! 
ok 
4> 
2

Erlang là hoàn toàn không đồng bộ, nếu bạn muốn có một câu trả lời đồng bộ để nhắn của bạn, bạn cần phải rõ ràng đang cho điều đó. Những gì có thể nói là các thông điệp trong một hộp thông điệp xử lý được xử lý tuần tự. Bất kỳ thư nào được gửi đến một tiến trình đều nằm trong hộp thông báo quá trình đó và quá trình sẽ chọn một thư từ quá trình hộp đó và sau đó chuyển sang thư tiếp theo theo thứ tự phù hợp. Đây là một hành động rất tuần tự và khối nhận thực hiện chính xác điều đó.

Có vẻ như bạn đã trộn lẫn đồng bộ và tuần tự như được đề cập đến.