2016-11-10 158 views
1

我刚刚学习ETSGenServer,我尝试在我的应用程序启动时初始化缓存。我很可能错误地设计了这个问题,导致我在下面描述的问题,所以任何反馈都会有帮助。使用GenServer初始化ETS缓存

当应用程序初始化时,: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 

我一定会错误在服务器回调中,只是对模块内部如何工作的基本误解。

回答

2

这有很多问题。我会尽力解释每一个:

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

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

If I try to modify the call function to GenServer.call(:my_table, :add) or GenServer.call(MODULE, :add), I get this error: ** (EXIT) no process.

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

So I try to directly update the :ets table:

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


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

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表名,这也是唯一命名的。

+1

很好的解释! 'GenServer.start_link(__ MODULE__,:ok,[name:__MODULE __])'正是我所期待的。这真的教会了我很多。 – jdesilvio