如何从列表和地图数据中创建多个GenServer进程?
在这两种方法中,我坚持如何通过给定的ID或组来映射进程,然后将存储的结构映射到筛选数据。
%{group => [users]}
实施。
我意识到组织将受到与用户相反的限制,所以我创建了一个使用组名作为键的流程模块。
我担心将来会有很多用户分成几组,所以我的问题是如何拆分当前的UserGroupServer
模块以保留许多由组名分隔的独立进程? 我想保持当前模块的功能,在init进程内按组列表,另外我不知道如何映射每个进程以通过user_id获取组?
目前,我只在Phoenix lib/myapp.ex
启动一个进程,方法是在子树列表中包含模块,这样我就可以直接在Channel中调用UserGroupServer
。
defmodule UserGroupServer do
use GenServer
## Client API
def start_link(opts []) do
GenServer.start_link(__MODULE__, :ok, opts)
end
def update_user_groups_state(server, data) do
{groups, user_id} = data
GenServer.call(server, {:clean_groups, user_id}, :infinity)
users = Enum.map(groups, fn(group) ->
GenServer.call(server, {:add_user_group, group, user_id}, :infinity)
end)
Enum.count(Enum.uniq(List.flatten(users)))
end
def get_user_groups(server, user_id) do
GenServer.call(server, {:get_user_groups, user_id})
end
def users_count_in_gorup(server, group) do
GenServer.call(server, {:users_count_in_gorup, group})
end
## Callbacks (Server API)
def init(_) do
{:ok, Map.new}
end
def handle_call({:clean_groups, user_id}, _from, user_group_dict) do
user_group_dict = user_group_dict
|> Enum.map(fn({gr, users}) -> {gr, List.delete(users, user_id)} end)
|> Enum.into(%{})
{:reply, user_group_dict, user_group_dict}
end
def handle_call({:add_user_group, group, user_id}, _from, user_group_dict) do
user_group_dict = if Map.has_key?(user_group_dict, group) do
Map.update!(user_group_dict, group, fn(users) -> [user_id | users] end)
else
Map.put(user_group_dict, group, [user_id])
end
{:reply, Map.fetch(user_group_dict, group), user_group_dict}
end
end
测试:
defmodule MyappUserGroupServerTest do
use ExUnit.Case, async: false
setup do
{:ok, server_pid} = UserGroupServer.start_link
{:ok, server_pid: server_pid}
end
test "add users", context do
c1 = UserGroupServer.update_user_groups_state(context[:server_pid], {[:a, :b, :c], 1})
assert(1 == c1)
c2 = UserGroupServer.update_user_groups_state(context[:server_pid], {[:c, :d], 2})
assert(2 == c2)
c3 = UserGroupServer.update_user_groups_state(context[:server_pid], {[:x], 2})
assert(1 == c3)
c4 = UserGroupServer.update_user_groups_state(context[:server_pid], {[:d], 1})
assert(1 == c4)
c5 = UserGroupServer.update_user_groups_state(context[:server_pid], {[:d, :c], 2})
assert(2 == c5)
end
end
旧方法%{user => [groups]}
监视器存储分配给user_id的组列表。 如何查找给定组中的用户? 我是否创建单独的进程来处理组和用户ID之间的关系? 我应该更改哪个用户组,然后映射它们?
服务器实施:
defmodule Myapp.Monitor do
use GenServer
def create(user_id) do
case GenServer.whereis(ref(user_id)) do
nil -> Myapp.Supervisor.start_child(user_id)
end
end
def start_link(user_id) do
GenServer.start_link(__MODULE__, [], name: ref(user_id))
end
def set_groups(user_pid, groups) do
try_call user_pid, {:set_groups, groups}
end
def handle_call({:set_groups, groups}, _from, state) do
{ :reply, groups, groups } # reset user groups on each set_group call.
end
defp ref(user_id) do
{:global, {:user, user_id}}
end
defp try_call(user_id, call_function) do
case GenServer.whereis(ref(user_id)) do
nil -> {:error, :invalid_user}
user_pid -> GenServer.call(user_pid, call_function)
end
end
end
主管:
defmodule Myapp.Supervisor do
use Supervisor
def start_link do
Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)
end
def start_child(user_id) do
Supervisor.start_child(__MODULE__, [user_id])
end
def init(:ok) do
supervise([worker(Myapp.Monitor, [], restart: :temporary)], strategy: :simple_one_for_one)
end
end
例:
Monitor.create(5)
Monitor.set_groups(5, ['a', 'b', 'c'])
Monitor.create(6)
Monitor.set_groups(6, ['a', 'b'])
Monitor.set_groups(6, ['a', 'c'])
# Monitor.users_in_gorup('a') # -> 2
# Monitor.users_in_gorup('b') # -> 1
# Monitor.users_in_gorup('c') # -> 2
# or eventually more desired:
# Monitor.unique_users_in_groups(['a', 'b', 'c']) # -> 2
# or return in set_groups unique_users_in_groups result
在跳转到进程和gen_servers之前,您总是需要考虑数据结构。
你打算如何添加数据? 多久? 你将如何查询它? 多久?
在你的例子中,你提到了三个操作:
使用Elixir中的最基本类型(列表和地图),您可以通过两种方式来安排数据:
%{user => [groups]}
) %{group => [users]}
) 对于这两种实现,您可以评估操作的速度。 对于%{user => [groups]}
:
O(1)
(只需更新地图中的键) O(n*m)
中的所有用户,其中n
是用户数, m
是组数(对于所有n
用户,您需要通过扫描可能的m
组名来检查它是否在组中) 对于%{group => [users]}
:
O(n*m)
(如果用户在那里,则需要扫描所有组,删除它,然后仅为新组设置它)如果设置的组仅将用户添加到新组而不先删除它,它只会在时间上与用户输入中的组数量成正比(不是所有组) O(1)
所有用户 - 只需查询地图 这表明,如果您的显示器快速更新并且查询次数较少,那么首次实施会更好。 如果你不经常地更新它,第二个更好,但是一直查询它。
在你没有任何actor或gen_server的情况下实现其中的一个解决方案,并且可以告诉它它的工作原理之后,你可能想把pid当成map键并重写算法。 您也可以考虑只使用一个进程来存储所有数据。 这也取决于你确切的问题。 祝你好运!
链接地址: http://www.djcxy.com/p/92563.html上一篇: How to create many GenServer processes from List and map data stored in them?