Trong chương "Lập trình đa lõi CPU" của Lập trình Erlang cuốn sách, Joe Armstrong đưa ra một ví dụ tốt đẹp của song song của một hàm bản đồ:Làm cách nào để tối ưu hóa vòng nhận cho hàng nghìn thư trong Erlang?
pmap(F, L) ->
S = self(),
%% make_ref() returns a unique reference
%% we'll match on this later
Ref = erlang:make_ref(),
Pids = map(fun(I) ->
spawn(fun() -> do_f(S, Ref, F, I) end)
end, L),
%% gather the results
gather(Pids, Ref).
do_f(Parent, Ref, F, I) ->
Parent ! {self(), Ref, (catch F(I))}.
gather([Pid|T], Ref) ->
receive
{Pid, Ref, Ret} -> [Ret|gather(T, Ref)]
end;
gather([], _) ->
[].
Nó hoạt động độc đáo, nhưng tôi tin rằng có một nút cổ chai trong đó gây ra nó hoạt động rất chậm trên danh sách với hơn 100.000 yếu tố.
Khi chức năng gather()
được thực hiện, nó bắt đầu khớp với một Pid
đầu tiên từ danh sách Pids
với thông báo trong hộp thư chính của quá trình. Nhưng nếu thư cũ nhất trong hộp thư không phải là từ số này rất Pid
thì sao? Sau đó, nó cố gắng tất cả các tin nhắn khác cho đến khi nó tìm thấy một trận đấu. Điều đó đang được nói, có một xác suất nhất định, trong khi thực hiện chức năng gather()
chúng ta sẽ phải lặp qua tất cả các thông báo hộp thư để tìm một kết quả phù hợp với một Pid
mà chúng tôi đã lấy từ danh sách Pids
. Đó là N * N trường hợp xấu nhất cho một danh sách các kích thước N.
Tôi thậm chí còn cố gắng chứng minh sự tồn tại của nút cổ chai này:
gather([Pid|T], Ref) ->
receive
{Pid, Ref, Ret} -> [Ret|gather(T, Ref)];
%% Here it is:
Other -> io:format("The oldest message in the mailbox (~w) did not match with Pid ~w~n", [Other,Pid])
end;
Làm thế nào tôi có thể tránh nút cổ chai này?
có vẻ như là một vấn đề rất đơn giản, ngạc nhiên là vẫn không có câu trả lời hay cho việc này. – cnst