使用GenServer初始化ETS缓存

时间:2016-11-10 18:42:17

标签: elixir gen-server

我刚学习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如何在模块内工作的基本误解。

1 个答案:

答案 0 :(得分:2)

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

  

IEX(1)&GT; MyApp.DataTable.add

     

**(退出)退出:GenServer.call(#PID&lt; 0.157.0&gt;,:add,5000)

     

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

     

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

这是因为您在自己上调用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

然后在__MODULE__中使用add

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表名,因此可能没有问题。也是唯一的名字。