多进程

  使用spawn创建一个新进程,其第一个参数是模块名、第二个参数是函数名、第三个参数是参数列表。spawn会返回一个进程标识符,通常叫做PID。

defmodule Spawn1 do
    def greet do
        receive do
            {sender, msg} ->
                send sender, { :ok, "Hello #{msg}" }
            # code
        end
        
    end
end

spawn(SpawnBasic, :greet, [])        #"Hello"

 

进程间发送消息

  使用send发送消息,第一个参数是接收方pid、第二个参数是要发送的消息,通常是原子或者元组。使用receive等待消息,它的用法比较像case。

#以下代码都在同一个文件中。
defmodule Spawn1 do def greet do
receive do {sender, msg} -> send sender, { :ok, "Hello #{msg}" } # code end end end pid = spawn(Spawn1, :greet, []) send pid, {self, "World!"} receive do {:ok, message} -> IO.puts message end

  上述代码如果想要发送第二条数据,就会导致iex被挂起。因为greet函数在处理完receive后,就退出了。我们永远等不来receive的相应。我们可以使用 after 指定receive在规定时间内未到达就超时。

receive do
    {:ok, message} ->
        IO.puts message
    after 500 ->    #receive在等待500毫秒后,消息未到达就会退出
        IO.puts "The greeter han gone away"
end

 

  我们可以使用递归来处理多条消息。greet:

def greet do
    receive do
        {sender, msg} ->
            send, sender, { :ok, "Hello, #{msg}" }
            greet
        end
    end
end

pid = spawn(Spawn1, :greet, [])
send pid, {self, "World!"}

receive do
    {:ok, message} ->
        IO.puts message            # "Hello, World!"
end

pid = spawn(Spawn1, :greet, [])
send pid, {self, "kermit!"}

receive do
    {:ok, message} ->
        IO.puts message            # "Hello, Kermit!"
    after 500 ->
        IO.puts "The greeter has gone away"
end

 

进程开销

  很小。

 

  进程调用exit(:boom)会以状态码99退出。

 

  关联两个进程,使用spawn_link会创建一个进程并和调用者相关联。

defmodule Link2 do 
    import :timer, only: [ sleep: 1 ]

    def sad_function do
        sleep 500
        exit(:boom)
    end
    def run do
        spawn_link(Link2, :sad_function, [])
        receive do
            msg ->
                IO.puts "<MESSAGE RECEIVED: #{inspect msg}"
        after 1000 ->
            IO.puts "Nothing ... "
        end
    end
end

  子进程结束,然后它会结束整个应用,这是关联进程的默认行为,当其中一个进程非正常退出时会把另一个进程也杀死。

  设置Peocess.flag(:trap_exit, true),可以捕获进程退出时的消息。

  创建进程时,可以使用spawn_monitor开启监控(创建 + 监控),也可以使用Process.monitor监控已经存在的进程。当使用Process.monitor时,在调用监控完成前被监控进程死了,就不会受到通知。然而,spawn_link和spawn_monitor操作符合原子性,所以总能捕获到错误。 

 

并行map

  普通map返回列表,该列表是某个收集的每个元素应用于某个函数的结果。并行版本做同样的事情,但每个元素在独立的进程里应用于函数。

defmodule Parallel do
    def pmap(collection, fun) do
        me = self
        collection
        |> Enum.map(fn (elem) ->     #双重嵌套函数
            spawn_link fn -> (send me, { self, fun.(elem) } ) end    #返回一组PID,每个PID内都运行了一个计算函数,将自身PID和结果发送给主进程
        end)
        |> Enum.map(fn (pid) ->    #在主进程中等待结果
            receive do {^pid, resuet } -> result end
        end)
    end
end

 

斐波拉契数服务器

  编写一个服务程序来计算斐波拉契数。当计算器准备好接受下一个数字时,它会发送 :ready 消息给调度器,如果仍有任务为完成,调度器会发送 :fib 消息给计算器;否则发送shutdown 给计算器。当计算器接受到 :fib 消息就计算给定的斐波拉契数,并以 :answer 返回结果。如果收到shutdown就退出。

  

defmodule FibSolver do    #计算模块
    def fib(scheduler) do
        send scheduler, {:ready, self}
        receive do
            { :fib, n, client} ->
                send client, { :answer, n, fib_calc(n), self}
                fib(scheduler)

            { :shutdown } ->
                exit(:normal)
        end
        
    end

    defp fib_calc(0) do
        0
    end

    defp fib_calc(1) do
        1
    end

    defp fib_calc(n) do
        fib_calc(n - 1) + fib_calc(n - 2)
    end
end


defmodule Scheduler do      #调度模块
    def run(num_process, module, func, to_calculate) do
        (1..num_process)
        |> Enum.map(fn (_) -> spawn(module, func, [self]) end)
        |> scheduler_process(to_calculate, [])
    end

    defp scheduler_process(processes, queue, results) do
        receive do
            {:ready, pid} when length(queue) > 0 ->
                [next | tail] = queue
                send pid, {:fib, next, self}
                scheduler_process(processes, tail, results)

            {:ready, pid} ->
                send pid, {:shutdown}
                if length(processes) > 1 do
                    scheduler_process(List.delete(processes, pid), queue, results)
                else
                    Enum.sort(results, fn {n1, _}, {n2, _} -> n1 <= n2 end)
                end

            {:answer, number, result, _pid}->
                scheduler_process(processes, queue, [ {number,result} | results])
        end
        
    end
end


to_process = [ 37, 37, 37, 37, 37, 37]    #外部调用模块

Enum.each 1..10, fn num_processes ->
    {time, result} = :timer.tc(Scheduler, :run, [num_processes, FibSolver, :fib, to_process])

    if num_processes == 1 do
        IO.puts inspect result
        IO.puts "\n #    time (s)"
    end

    :io.format "~2B     ~.2f~n", [num_processes, time/1000000.0]
end

 

posted @ 2019-09-11 18:31  GodL  阅读(252)  评论(0编辑  收藏  举报