我正在通过ASP.NET构建一个基于Web的基本聊天,它会轮询Windows服务中托管的WCF服务。我是一个在多线程应用程序中管理共享资源的新手,所以我在方法中有几个关于线程安全的问题。
服务核心是一个名为ServerManager的Singleton类,它维护一个ChatServer对象的Dictionary,它只被初始化和填充一次,并且从不再添加或删除。来自WCF服务的调用将引用客户端所连接的实例的密钥(serverID),并且连接"至。在任何给定时间,多个线程可能会进入服务,多个线程同时调用ServerManager的方法。
访问Dictionary _servers与锁定块同步,同时获取对所请求的ChatServer对象的引用,然后在锁定块之外使用本地引用。我不希望所有ChatServers被一个ChatServer的读/写阻止。
在读取/写入_messages对象期间,我还在ChatServer实例中包含了同步。
在获取对请求对象的引用时,同步对_servers的访问是否是线程安全的,但是然后在锁定块之外使用该引用(currentServer)?换句话说,只要访问这些对象中的共享数据,多个线程可以安全地访问Dictionary中的不同元素吗?
像在ChatServer.GetNewMessages()中那样将字符串List返回给调用者是否是线程安全的?
如果newMessages是一个新的ChatMessage对象的List,那么在返回ChatMessage列表时,我需要做些什么来确保GetNewMessages不会导致线程安全问题?
< / LI> 醇>这是一个类的片段(注意:此代码已经过简化以说明问题)
class ServerManager
{
private static readonly ServerManager _instance = new ServerManager();
private Dictionary<Int32, ChatServer> _servers = new Dictionary<Int32, ChatServer>();
private Object _lock = new Object();
private ServerManager()
{
_servers.Add(1, new ChatServer());
_servers.Add(2, new ChatServer());
_servers.Add(3, new ChatServer());
}
public static ServerManager Instance
{
get { return _instance; }
}
public void AddMessage(Int32 serverID, String message)
{
ChatServer currentServer;
lock (_lock)
{
currentServer = _servers[serverID];
}
currentServer.AddMessage(message);
}
public List<String> GetNewMessages(Int32 serverID)
{
ChatServer currentServer;
lock (_lock)
{
currentServer = _servers[serverID];
}
return currentServer.GetNewMessages();
}
}
class ChatServer
{
private List<String> _messages = new List<string>();
private Object _lock = new Object();
public ChatServer() { }
public void AddMessage(String message)
{
lock (_lock)
{
_messages.Add(message);
}
}
public List<String> GetNewMessages()
{
List<String> newMessages = new List<String>();
lock (_lock)
{
newMessages.AddRange(_messages);
}
return newMessages;
}
}
答案 0 :(得分:1)
获取时,是否可以同步访问_servers的线程安全性 引用所请求的对象,但随后使用该引用 (currentServer)在锁定块之外?换句话说,可以 多个线程安全地访问内部的不同元素 字典只要访问这些对象中的共享数据即可 同步?
是的,它是线程安全的,假设元素本身是线程安全的。在这种情况下,您正在处理ChatServer
个对象,这些对象本身必须保证线程安全性
在ChatServer.GetNewMessages()中将String列表返回给调用者是否是线程安全的?
是 - 您正在创建本地变量,然后返回该变量。线程之间共享的所有资源都在锁定范围内(在您的示例中仅共享_messages
)。
如果newMessages是一个新的ChatMessage对象的List,那么 我需要做的是确保GetNewMessages不会导致 返回ChatMessage列表时出现线程安全问题?
不 - 您GetNewMessages
中的代码无需更改。 但是,ChatMessage
必须对线程安全做出自己的保证。实际上,如果GetNewMessages
本身不是线程安全的,那么编码ChatMessage
就不可能提供线程安全性(当然,不能克隆对象)。
<强> 建议 强>:
您的代码目前已被破解:_servers
和_lock
未初始化 - 请在您的帖子中进行修改,以便更轻松地复制和删除粘贴
您ServerManager
中不需要任何锁定 - 假设_servers
从未添加或删除。
您ChatServer
课程中可能需要或可能不需要锁定。正如现在所写,它没有线程安全问题(也就是说,lock
是正确的)。但是,根据课程的使用方式,您可能需要查看ReaderWriterLock
或ConcurrentBag<T>
您的ServerManager
类可以在没有锁的情况下编写如下,并且是线程安全的:
class ServerManager
{
private static readonly ServerManager _instance = new ServerManager();
private readonly Dictionary<Int32, ChatServer> _servers;
private ServerManager()
{
_servers = new Dictionary<int, ChatServer>();
_servers.Add(1, new ChatServer());
_servers.Add(2, new ChatServer());
_servers.Add(3, new ChatServer());
}
public static ServerManager Instance
{
get { return _instance; }
}
public void AddMessage(Int32 serverID, String message)
{
_servers[serverID].AddMessage(message);
}
public List<String> GetNewMessages(Int32 serverID)
{
return _servers[serverID].GetNewMessages();
}
}
答案 1 :(得分:1)
这个很长的答案的简短摘要:您的代码是线程安全的,但可以改进。
ChatServer对象的字典,它只被初始化和填充一次,从不再添加或删除。
如果它是只读的,您不需要同步Dictionary
的访问权限。但是,如果它是只读的,请使用ReadOnlyDictionary
类在代码中将其显式化。
在获取对请求对象的引用时,同步对_servers的访问是否是线程安全的,但是然后在锁定块之外使用该引用(currentServer)?换句话说,只要访问这些对象中的共享数据,多个线程可以安全地访问Dictionary中的不同元素吗?
它是线程安全的,但最好使用专门为您执行此操作的数据结构。例如ConcurrentDictionary
。线程安全的数据结构通常效率更高,因为大多数数据结构使用Interlocked
进行线程同步。
你的字典是只读的,所以它甚至不需要同步。
在ChatServer.GetNewMessages()中将String列表返回给调用者是否是线程安全的?
它是每个电话的全新列表,它是线程安全的。但是你为什么要回归List<string>
?客户是否应该能够修改返回的集合(Add
,Remove
,Clear
)?选择客户端所需的最小界面,例如IEnumerable<string>
或IReadOnlyList<string>
,然后使用List<T>.AsReadOnly()
将其返回。您仍然需要列表的副本,AsReadOnly()
只在列表周围创建一个包装器,而不是只读副本。
更好的选择是将消息存储在线程安全的集合中,例如ConcurrentQueue<T>
。您可以直接在客户端中使用队列,也可以在每次使用ConcurrentQueue<T>.ToArray()
调用GetNewMessages
时返回快照。
在这里,您可以看到使方法返回接口的好处。第一个选项实际上返回ReadOnlyCollection<string>
,string[]
上的第二个选项,但由于这两种类型都实现了IEnumerable<string>
和IReadOnlyList<string>
,因此客户端不会受到您选择的影响。它是您可以随时更改的实施细节。
如果newMessages是一个新的ChatMessage对象的List,那么在返回ChatMessage列表时,我需要做些什么来确保GetNewMessages不会导致线程安全问题?
让ChatMessage
不可变。不可变类型始终是线程安全的。