使用 GenServer 初始化 ETS 缓存
Initialize ETS cache using GenServer
我刚刚了解 ETS
和 GenServer
,我正尝试在我的应用程序启动时初始化缓存。很可能是我设计的不正确,这导致了我在下面描述的问题,所以对此的任何反馈都会有所帮助。
当应用程序初始化时,:ets
table 是通过 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"}]
到目前为止一切顺利...但现在我想更新 table。所以我添加了一个 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
table:
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
在模块内部的工作方式存在根本性的误解。
这有很多问题。我会尝试解释每一个:
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 tables 不允许除创建 table 的进程之外的任何人写入 table。您可以通过将 :public
作为选项传递给 :ets.new
的最后一个参数来使 table public。这将允许任何进程写入 table.
有很多方法可以解决这个问题。一种是在 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 table 名称,这也有独特的名字。
我刚刚了解 ETS
和 GenServer
,我正尝试在我的应用程序启动时初始化缓存。很可能是我设计的不正确,这导致了我在下面描述的问题,所以对此的任何反馈都会有所帮助。
当应用程序初始化时,:ets
table 是通过 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"}]
到目前为止一切顺利...但现在我想更新 table。所以我添加了一个 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
table:
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
在模块内部的工作方式存在根本性的误解。
这有很多问题。我会尝试解释每一个:
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
.
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 tables 不允许除创建 table 的进程之外的任何人写入 table。您可以通过将 :public
作为选项传递给 :ets.new
的最后一个参数来使 table public。这将允许任何进程写入 table.
有很多方法可以解决这个问题。一种是在 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 table 名称,这也有独特的名字。