【问题标题】:Initialize ETS cache using GenServer使用 GenServer 初始化 ETS 缓存
【发布时间】:2017-03-24 21:46:35
【问题描述】:

我刚刚了解ETSGenServer,并且我正在尝试在我的应用程序启动时初始化缓存。很有可能我的设计不正确,这导致了我在下面描述的问题,因此对此的任何反馈都会有所帮助。

当应用初始化时,:ets 表是通过worker 创建的。

def start_link do
  GenServer.start_link(__MODULE__, :ok)
end

def init(:ok) do
  tab = :ets.new(:my_table, [:set, :named_table])
  :ets.insert(:my_table, {1, "one"})
  {:ok, tab}
end

def lookup(key) do
  :ets.lookup(:my_table, key)
end

iex(1)> MyApp.DataTable.lookup(1)
[{1, "one"}]

到目前为止一切顺利...但现在我想更新该表。所以我加了一个call

def add do
  GenServer.call(self(), :add)
end

def handle_call(:add, _from, tab) do
  tab = :ets.insert(:my_table, {2, "two"})
  {:reply, lookup(2), tab}
end

iex(1)> MyApp.DataTable.add
** (exit) exited in: GenServer.call(#PID<0.157.0>, :add, 5000)
    ** (EXIT) process attempted to call itself
    (elixir) lib/gen_server.ex:598: GenServer.call/3

如果我尝试将call 函数修改为GenServer.call(:my_table, :add)GenServer.call(__MODULE__, :add),我会收到此错误:** (EXIT) no process。显然,我对call 做错了。

所以我尝试直接更新:ets表:

def add_direct do
  :ets.insert(:my_table, {2, "two"})
end

iex(1)> MyApp.DataTable.add_direct
** (ArgumentError) argument error
   (stdlib) :ets.insert(:my_table, {2, "two"})
   (my_app) lib/my_app/data_table.ex:17:
     MyApp.DataTable.add_direct/0

当我运行:ets.all() 时,我可以看到:my_table。所以最后我尝试在iex更新它:

iex(2)> :ets.insert(:my_table, {2, "two"})
** (ArgumentError) argument error
    (stdlib) :ets.insert(:my_table, {2, "two"})

为了确保我没有完全发疯,我运行了这个确实有效的健全性检查:

iex(2)> :ets.new(:my_table2, [:set, :named_table])
:my_table2
iex(3)> :ets.insert(:my_table2, {2, "two"})
true

我一定是在服务器回调中出错了,只是对 :ets 在模块内的工作方式存在根本性的误解。

【问题讨论】:

    标签: elixir gen-server


    【解决方案1】:

    这有很多问题。我将尝试解释每一个:

    iex(1)> MyApp.DataTable.add

    **(退出)退出:GenServer.call(#PID, :add, 5000)

    ** (EXIT) 进程试图调用自己

    (elixir) lib/gen_server.ex:598: GenServer.call/3

    这是因为您在 self 上调用 GenServer 方法。您应该在 start_link 返回的 PID 上调用它。

    如果我尝试将调用函数修改为 GenServer.call(:my_table, :add) 或 GenServer.call(MODULE, :add),我会收到此错误:** (EXIT)没有进程。

    第一个失败是因为:my_table 不是已注册的 GenServer 名称。第二个失败,因为您没有使用名称注册 GenServer。

    所以我尝试直接更新 :ets 表:

    这是因为默认情况下,ETS 表不允许除创建表的进程之外的任何人写入表。您可以通过将:public 作为选项传递给:ets.new 的最后一个参数来公开该表。这将允许任何进程写入该表。


    有很多方法可以解决这个问题。一种是接受add中的PID:

    def add(pid) do
      GenServer.call(pid, :add)
    end
    

    然后这样称呼它:

    iex(1)> {:ok, pid} = A.start_link
    {:ok, #PID<0.86.0>}
    iex(2)> A.add(pid)
    1
    [{2, "two"}]
    

    另一种解决方案是在创建进程时为其注册一个名称:

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

    然后在add中使用__MODULE__

    def add do
      GenServer.call(__MODULE__, :add)
    end
    
    iex(1)> A.start_link
    {:ok, #PID<0.86.0>}
    iex(2)> A.add
    1
    [{2, "two"}]
    

    使用名称注册进程还意味着您不能在第一个进程处于活动状态时注册具有相同名称的另一个进程,但这在这里可能没问题,因为您使用的是固定的 ETS 表名称,它也是唯一命名的。

    【讨论】:

    • 很好的解释! GenServer.start_link(__MODULE__, :ok, [name: __MODULE__]) 正是我想要的。这真的教会了我很多东西。
    • 未将缓存访问控制设置为 :public 位我。 ** (ArgumentError) argument error 的错误代码也不是很有帮助。 RTFD...
    猜你喜欢
    • 2018-04-04
    • 1970-01-01
    • 2021-08-28
    • 1970-01-01
    • 1970-01-01
    • 2013-07-25
    • 1970-01-01
    • 2019-04-25
    • 2015-01-17
    相关资源
    最近更新 更多