Elixir 使用GenServer初始化ETS缓存

Elixir 使用GenServer初始化ETS缓存,elixir,gen-server,Elixir,Gen Server,我刚刚学习了ETS和GenServer,我正在尝试在我的应用程序启动时初始化缓存。很有可能是我设计的不正确导致了我在下面描述的问题,所以任何关于这方面的反馈都会很有帮助 当应用程序初始化时,将通过工作者创建:ets表 def start_link do GenServer.start_link(__MODULE__, :ok) end def init(:ok) do tab = :ets.new(:my_table, [:set, :named_table]) :ets.inse

我刚刚学习了
ETS
GenServer
,我正在尝试在我的应用程序启动时初始化缓存。很有可能是我设计的不正确导致了我在下面描述的问题,所以任何关于这方面的反馈都会很有帮助

当应用程序初始化时,将通过
工作者创建
:ets

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"}]
到目前为止还不错…但现在我想更新那个表。因此,我添加了一个
调用

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
当我运行
: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
在模块内如何工作的一个基本误解

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

iex(1)>MyApp.DataTable.add

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

**(退出)进程试图调用自身

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

这是因为您正在对self调用GenServer方法。您应该在
start\u link
返回的PID上调用它

如果尝试将调用函数修改为GenServer.call(:my_table,:add)或GenServer.call(MODULE,:add),则会出现以下错误:*(退出)无进程

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

因此,我尝试直接更新:ets表:

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


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

def add(pid) do
  GenServer.call(pid, :add)
end
def add do
  GenServer.call(__MODULE__, :add)
end
然后像这样称呼它:

iex(1)> {:ok, pid} = A.start_link
{:ok, #PID<0.86.0>}
iex(2)> A.add(pid)
1
[{2, "two"}]
然后在
添加
中使用
\uuu模块\uuuuuuuu

def add(pid) do
  GenServer.call(pid, :add)
end
def add do
  GenServer.call(__MODULE__, :add)
end
iex(1)>A.start\u链接
{:好的,#PID}
iex(2)>A.add
1.
[{2,“两”}]

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

很好的解释
GenServer.start\u链接(\uuuu模块\uuuu,:好的,[name:\uuu模块])
正是我要找的。这真的教会了我很多。没有将缓存访问控制设置为
:public
让我有点为难。
**(ArgumentError)argument error
的错误代码也没有多大帮助。RTFD。。。
iex(1)> A.start_link
{:ok, #PID<0.86.0>}
iex(2)> A.add
1
[{2, "two"}]