Elixir 并发容错语言

FreeGuideOnline 18阅读 2026-07-13

elixir iex> IO.puts("Hello, Elixir!") Hello, Elixir! :ok


## Elixir 快速入门

### 核心数据类型

```elixir
iex> 42                     # 整数
iex> 3.14                   # 浮点数
iex> "hello"                # 字符串(UTF-8二进制)
iex> :atom                  # 原子(类似符号常量)
iex> [1, 2, 3]              # 列表(链表)
iex> {1, :ok, "result"}     # 元组
iex> %{:a => 1, :b => 2}    # 映射(Map)

模式匹配

Elixir 中 = 不是赋值,而是匹配。

iex> {a, b, c} = {:hello, "world", 42}
{:hello, "world", 42}
iex> a
:hello
iex> c
42

# 列表也可以匹配
iex> [head | tail] = [1, 2, 3]
[1, 2, 3]
iex> head
1
iex> tail
[2, 3]

模式匹配被广泛用于函数定义、控制流等,让代码异常清晰。

函数与管道

定义有名函数:

defmodule Math do
  def sum(a, b) do
    a + b
  end
end

匿名函数:

add = fn a, b -> a + b end
add.(1, 2)  # 调用时有个点

管道操作符 |> 将前一个表达式的结果作为第一个参数传递给下一个函数,使数据转换链一目了然:

"hello world"
|> String.upcase()       # "HELLO WORLD"
|> String.split()        # ["HELLO", "WORLD"]
|> Enum.join("-")        # "HELLO-WORLD"

并发基石:Elixir 进程

创建进程与消息传递

Elixir 的并发模型基于 Actor 模型。每个进程独立运行,通过消息传递通信,不共享内存。

使用 spawn 创建一个新进程:

pid = spawn(fn ->
  receive do
    {:hello, msg} -> IO.puts("Received: #{msg}")
  end
end)

# 向进程发送消息
send(pid, {:hello, "World"})

进程会一直循环接收消息,除非结束。更常见的方式是使用递归保持进程活跃:

defmodule Counter do
  def start do
    spawn(fn -> loop(0) end)
  end

  defp loop(count) do
    receive do
      :increment -> loop(count + 1)
      {:get, caller} ->
        send(caller, {:count, count})
        loop(count)
    end
  end
end

counter = Counter.start()
send(counter, :increment)
send(counter, {:get, self()})   # self() 返回当前进程PID

receive do
  {:count, value} -> IO.puts("Count is #{value}")
end

进程隔离与错误隔离

进程之间的崩溃不会互相影响。如果一个进程崩溃,只有它自己会终止,其他进程毫不知情。这是实现容错的基础。

# 一个会崩溃的进程
spawn(fn -> raise "oops" end)
# 当前进程照常运行
IO.puts("I'm still alive")

构建容错系统:OTP 监督树

实际开发中,我们不会从原始 spawnreceive 开始,而是使用 OTP 提供的行为(Behaviour),如 GenServerSupervisor

GenServer:通用服务器模式

GenServer 封装了客户端 - 服务器交互模式,包括处理同步/异步调用、状态管理、代码热升级等。

defmodule Stack do
  use GenServer

  # 客户端 API
  def start_link(initial_stack) do
    GenServer.start_link(__MODULE__, initial_stack, name: __MODULE__)
  end

  def push(element) do
    GenServer.cast(__MODULE__, {:push, element})
  end

  def pop do
    GenServer.call(__MODULE__, :pop)
  end

  # 服务端回调
  @impl true
  def init(stack) do
    {:ok, stack}
  end

  @impl true
  def handle_cast({:push, element}, stack) do
    {:noreply, [element | stack]}
  end

  @impl true
  def handle_call(:pop, _from, [head | tail]) do
    {:reply, head, tail}
  end
end

cast 是异步,call 是同步。

Supervisor:监督者

Supervisor 负责启动、停止、监控子进程,并定义崩溃时的重启策略。

defmodule Stack.App do
  use Application

  def start(_type, _args) do
    children = [
      {Stack, []}   # 子进程规格,启动 Stack 进程
    ]

    opts = [strategy: :one_for_one, name: Stack.Supervisor]
    Supervisor.start_link(children, opts)
  end
end
  • 策略 :one_for_one:当一个子进程挂掉,仅重启该进程。
  • 其他策略如 :one_for_all:rest_for_one 可以应对不同的依赖关系。

启动 Application 后,如果 Stack 进程因异常崩溃,Supervisor 会自动以初始状态重启它,外部客户端感觉不到中断(除非在崩溃瞬间有请求被丢弃,但系统自我修复)。

Elixir 的并发工具箱

Task:并发执行单元

Task 提供了一种便捷的方式,用于执行一次性计算并获取结果。

task = Task.async(fn -> heavy_computation() end)
# ... 做其他事情
result = Task.await(task)   # 等待并获取结果

Agent:简单的状态抽象

AgentGenServer 的简化版,仅用于状态管理:

{:ok, agent} = Agent.start_link(fn -> 0 end)
Agent.update(agent, fn count -> count + 1 end)
Agent.get(agent, fn count -> count end)  # 1