Elixir GenServer #

一、GenServer概述 #

1.1 什么是GenServer #

GenServer是Elixir中最常用的OTP行为,实现了客户端-服务器模式。

1.2 GenServer特性 #

  • 同步和异步调用
  • 状态管理
  • 超时处理
  • 热代码升级
  • 监督树集成

二、实现GenServer #

2.1 基本结构 #

elixir
defmodule Counter do
  use GenServer

  def start_link(initial \\ 0) do
    GenServer.start_link(__MODULE__, initial, name: __MODULE__)
  end

  def init(initial) do
    {:ok, initial}
  end

  def increment do
    GenServer.cast(__MODULE__, :increment)
  end

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

  def handle_cast(:increment, state) do
    {:noreply, state + 1}
  end

  def handle_call(:get, _from, state) do
    {:reply, state, state}
  end
end

2.2 启动GenServer #

elixir
iex(1)> Counter.start_link(0)
{:ok, #PID<0.123.0>}

iex(2)> Counter.increment()
:ok

iex(3)> Counter.get()
1

三、回调函数 #

3.1 init #

elixir
def init(args) do
  {:ok, state}
end

def init(args) do
  {:stop, reason}
end

def init(args) do
  :ignore
end

3.2 handle_call #

处理同步调用:

elixir
def handle_call(:get, _from, state) do
  {:reply, state, state}
end

def handle_call({:set, new_state}, _from, _state) do
  {:reply, :ok, new_state}
end

def handle_call(:stop, _from, state) do
  {:stop, :normal, :ok, state}
end

返回值:

  • {:reply, reply, new_state}
  • {:reply, reply, new_state, timeout | :hibernate}
  • {:noreply, new_state}
  • {:noreply, new_state, timeout | :hibernate}
  • {:stop, reason, reply, new_state}
  • {:stop, reason, new_state}

3.3 handle_cast #

处理异步调用:

elixir
def handle_cast(:increment, state) do
  {:noreply, state + 1}
end

def handle_cast({:set, new_state}, _state) do
  {:noreply, new_state}
end

def handle_cast(:stop, state) do
  {:stop, :normal, state}
end

返回值:

  • {:noreply, new_state}
  • {:noreply, new_state, timeout | :hibernate}
  • {:stop, reason, new_state}

3.4 handle_info #

处理其他消息:

elixir
def handle_info(:tick, state) do
  IO.puts("Tick: #{state}")
  {:noreply, state}
end

def handle_info({:DOWN, _ref, :process, _pid, _reason}, state) do
  {:noreply, state}
end

def handle_info(_msg, state) do
  {:noreply, state}
end

3.5 terminate #

elixir
def terminate(reason, state) do
  IO.puts("Terminating: #{reason}")
  :ok
end

3.6 code_change #

elixir
def code_change(_old_vsn, state, _extra) do
  {:ok, state}
end

四、API设计 #

4.1 完整示例 #

elixir
defmodule Stack do
  use GenServer

  def start_link(initial \\ []) do
    GenServer.start_link(__MODULE__, initial, name: __MODULE__)
  end

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

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

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

  def clear do
    GenServer.cast(__MODULE__, :clear)
  end

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

  def init(initial) do
    {:ok, initial}
  end

  def handle_cast({:push, item}, state) do
    {:noreply, [item | state]}
  end

  def handle_cast(:clear, _state) do
    {:noreply, []}
  end

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

  def handle_call(:pop, _from, []) do
    {:reply, nil, []}
  end

  def handle_call(:peek, _from, [head | _] = state) do
    {:reply, head, state}
  end

  def handle_call(:peek, _from, []) do
    {:reply, nil, []}
  end

  def handle_call(:size, _from, state) do
    {:reply, length(state), state}
  end
end

4.2 使用示例 #

elixir
iex(1)> Stack.start_link([1, 2, 3])
{:ok, #PID<0.123.0>}

iex(2)> Stack.peek()
1

iex(3)> Stack.pop()
1

iex(4)> Stack.push(0)
:ok

iex(5)> Stack.size()
3

五、超时与hibernate #

5.1 超时 #

elixir
def handle_call(:slow, _from, state) do
  Process.sleep(5000)
  {:reply, :ok, state}
end

def handle_info(:timeout, state) do
  IO.puts("Timeout!")
  {:noreply, state}
end

5.2 hibernate #

elixir
def handle_call(:get, _from, state) do
  {:reply, state, state, :hibernate}
end

六、命名注册 #

6.1 原子名称 #

elixir
GenServer.start_link(__MODULE__, [], name: :my_server)
GenServer.call(:my_server, :get)

6.2 全局名称 #

elixir
GenServer.start_link(__MODULE__, [], name: {:global, :my_server})
GenServer.call({:global, :my_server}, :get)

6.3 via注册 #

elixir
defmodule Registry do
  def start_link do
    Registry.start_link(keys: :unique, name: MyApp.Registry)
  end
end

GenServer.start_link(__MODULE__, [], name: {:via, Registry, {MyApp.Registry, :my_server}})
GenServer.call({:via, Registry, {MyApp.Registry, :my_server}}, :get)

七、监控其他进程 #

7.1 监控进程 #

elixir
def handle_call({:monitor, pid}, _from, state) do
  ref = Process.monitor(pid)
  {:reply, :ok, Map.put(state, ref, pid)}
end

def handle_info({:DOWN, ref, :process, pid, reason}, state) do
  IO.puts("Process #{inspect(pid)} down: #{reason}")
  {:noreply, Map.delete(state, ref)}
end

八、定时任务 #

8.1 使用Process.send_after #

elixir
def init(args) do
  schedule_work()
  {:ok, args}
end

def handle_info(:work, state) do
  do_work()
  schedule_work()
  {:noreply, state}
end

defp schedule_work do
  Process.send_after(self(), :work, 60_000)
end

九、最佳实践 #

9.1 API封装 #

elixir
defmodule MyServer do
  use GenServer

  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  def operation(arg) do
    GenServer.call(__MODULE__, {:operation, arg})
  end

  def async_operation(arg) do
    GenServer.cast(__MODULE__, {:async_operation, arg})
  end
end

9.2 错误处理 #

elixir
def handle_call({:divide, a, b}, _from, state) do
  try do
    result = a / b
    {:reply, {:ok, result}, state}
  rescue
    ArithmeticError ->
      {:reply, {:error, :division_by_zero}, state}
  end
end

9.3 状态验证 #

elixir
def handle_call({:set, new_state}, _from, state) do
  if valid?(new_state) do
    {:reply, :ok, new_state}
  else
    {:reply, {:error, :invalid_state}, state}
  end
end

十、总结 #

本章学习了:

回调 用途
init/1 初始化
handle_call/3 同步调用
handle_cast/2 异步调用
handle_info/2 其他消息
terminate/2 终止处理
code_change/3 热升级

准备好学习Supervisor了吗?让我们进入下一章。

最后更新:2026-03-27