Erlang并发蒙特卡洛Pi值估算(多Actor与迭代数)实现问题咨询
Alright, let's figure out how to build that concurrent Monte Carlo Pi estimator in Erlang. You already have the non-concurrent version down, so we just need to split the work across X actors, collect their results, and crunch the final number. Here's a step-by-step solution with code and explanations:
Step 1: Core Idea
Instead of handling all N points in one process, we split the workload into X chunks. Each actor handles a subset of the points, counts how many fall inside the unit circle, and sends that count back to the main process. The main process sums all the counts and calculates Pi using the same formula as your non-concurrent version: 4 * (total_count / N).
Step 2: Full Concurrent Implementation
Here's a complete module that includes both your original non-concurrent function and the new concurrent one:
-module(pi_montecarlo). -export([montecarlo/1, montecarlo/2]). % Non-concurrent version (your original logic) montecarlo(N) -> montecarlo(N, 1). % Concurrent version: N total points, X actors montecarlo(N, X) when X > 0, N > 0 -> PerActor = N div X, Remainder = N rem X, % Spawn first X-1 workers with equal point counts Pids = spawn_workers(X-1, PerActor, self()), % Spawn last worker to handle any remaining points LastPid = spawn_worker(PerActor + Remainder, self()), AllPids = [LastPid | Pids], % Gather results from all workers TotalCount = collect_results(AllPids, 0), % Calculate final Pi estimate 4 * (TotalCount / N). spawn_workers(0, _, _) -> []; spawn_workers(Num, Points, Parent) -> Pid = spawn_worker(Points, Parent), [Pid | spawn_workers(Num-1, Points, Parent)]. spawn_worker(Points, Parent) -> spawn(fun() -> worker(Points, Parent) end). worker(Points, Parent) -> % Initialize unique random seed for each worker <<A:32, B:32, C:32>> = crypto:strong_rand_bytes(12), rand:seed(exsplus, {A,B,C}), Count = count_in_circle(Points, 0), Parent ! {self(), Count}. count_in_circle(0, Count) -> Count; count_in_circle(PointsLeft, Count) -> X = rand:uniform(), Y = rand:uniform(), case X*X + Y*Y =< 1.0 of true -> count_in_circle(PointsLeft-1, Count+1); false -> count_in_circle(PointsLeft-1, Count) end. collect_results([], Total) -> Total; collect_results([Pid | Rest], Total) -> receive {Pid, Count} -> collect_results(Rest, Total + Count) end.
Step 3: Breakdown of Key Parts
montecarlo/2(Main Function):- Splits the total points
Ninto equal chunks, and handles any remainder by assigning extra points to the last worker. This ensures we process allNpoints without missing any. - Spawns all worker processes and waits to collect their results.
- Splits the total points
Worker Processes:
- Each worker initializes its own random seed using
crypto:strong_rand_bytes—this avoids duplicate random sequences across processes, which is critical for getting accurate Pi estimates. - Runs the same point-counting logic as your non-concurrent code, but only for its assigned subset of points.
- Sends its final count back to the main process as a message tagged with its PID.
- Each worker initializes its own random seed using
Result Collection:
- The main process waits to receive a message from each specific worker PID, sums up all the counts, and computes the final Pi value using the standard Monte Carlo formula.
Step 4: Testing It Out
Try running these commands in the Erlang shell to see it in action:
% Non-concurrent (original behavior) pi_montecarlo:montecarlo(1000000). % Concurrent with 4 actors pi_montecarlo:montecarlo(1000000, 4).
You should notice the concurrent version runs faster (especially with larger N) since the work is spread across multiple processes.
Notes to Keep in Mind
- The guard clause
when X > 0, N > 0ensures we only accept valid positive integers for both parameters. - Using
crypto:strong_rand_bytesfor seed initialization is more reliable than timestamps, as it guarantees unique seeds for every worker. - The result collection logic is tied to specific worker PIDs, so you don't have to worry about missing or misordering results.
内容的提问来源于stack exchange,提问作者Anthony Nicastro

