简体   繁体   English

这种共享字典的方法是否线程安全?

[英]Is this approach to a shared Dictionary thread-safe?

I am constructing a basic web-based chat through ASP.NET which polls a WCF Service hosted inside a Windows Service. 我正在通过ASP.NET构建一个基于Web的基本聊天,该聊天将轮询Windows服务中托管的WCF服务。 I am quite new to managing shared resources in a multi-threaded application, so I have several questions about thread-safety in my approach. 我在管理多线程应用程序中的共享资源方面还很陌生,因此我对方法中的线程安全性有一些疑问。

The service core is a Singleton class called ServerManager which maintains a Dictionary of ChatServer objects which is initialized and populated only once, and never added to or removed from again. 服务核心是一个称为ServerManager的Singleton类,该类维护ChatServer对象的Dictionary,该字典仅被初始化和填充一次,并且永远不会添加或删除。 Calls from the WCF service will reference the key (serverID) of the instance the client is "connected" to. 从WCF服务进行的调用将引用客户端“连接”到的实例的密钥(服务器ID)。 At any given time, multiple calls may be coming into the service, with multiple threads making concurrent calls to ServerManager's methods. 在任何给定时间,服务中可能有多个调用,多个线程同时调用ServerManager的方法。

Access to the Dictionary _servers is synchronized with the lock block while obtaining a reference to the requested ChatServer object and then the local reference is then used outside of the lock block. 在获取对请求的ChatServer对象的引用的同时,对字典_servers的访问与锁定块同步,然后在锁定块外部使用本地引用。 I don't want all ChatServers to be blocked by a read/write to just one ChatServer. 我不希望仅对一个ChatServer进行读/写操作来阻止所有ChatServer。

I have also included synchronization within a ChatServer instance during read/writes to the _messages object. 在对_messages对象的读/写过程中,我还包括了ChatServer实例中的同步。

  1. Is it thread-safe to synchronize access to _servers when getting the reference to the requested object, but then utilize that reference (currentServer) outside of the lock block? 获取对所请求对象的引用,然后在锁块之外利用该引用(currentServer),同步对_servers的访问是否具有线程安全性? In other words, can multiple threads safely access different elements within the Dictionary as long as access shared data within these objects is synchronized? 换句话说,只要同步访问这些对象中的共享数据,多个线程是否可以安全地访问Dictionary中的不同元素?

  2. Is it thread-safe to return the List of String to the caller as I do in ChatServer.GetNewMessages()? 像在ChatServer.GetNewMessages()中一样,将字符串列表返回给调用方是否是线程安全的?

  3. If newMessages was instead a List of some new ChatMessage object, what would I need to do to ensure that GetNewMessages won't cause thread-safety issues when returning the List of ChatMessage? 如果newMessages而是一些新的ChatMessage对象的列表,我该怎么做才能确保GetNewMessages在返回ChatMessage列表时不会引起线程安全问题?

Here is a snippet of the classes (Note: this code has been simplified to illustrate the problem) 这是这些类的摘要(注意:此代码已简化以说明问题)

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;
    }
}

Is it thread-safe to synchronize access to _servers when getting the reference to the requested object, but then utilize that reference (currentServer) outside of the lock block? 获取对所请求对象的引用,然后在锁块之外利用该引用(currentServer),同步对_servers的访问是否具有线程安全性? In other words, can multiple threads safely access different elements within the Dictionary as long as access shared data within these objects is synchronized? 换句话说,只要同步访问这些对象中的共享数据,多个线程是否可以安全地访问Dictionary中的不同元素?

Yes, it's thread safe, assuming the element itself is thread-safe. 是的,假设元素本身是线程安全的,那么它是线程安全的。 In this case, you're dealing with ChatServer objects, which themselves must guarantee thread-safety 在这种情况下,您要处理的ChatServer对象本身必须保证线程安全。

Is it thread-safe to return the List of String to the caller as I do in ChatServer.GetNewMessages()? 像在ChatServer.GetNewMessages()中一样,将字符串列表返回给调用方是否是线程安全的?

Yes - you're creating a local variable which is then returned. 是的-您正在创建一个本地变量,然后将其返回。 All resources shared between threads are in the lock scope (only _messages is shared in your example). 线程之间共享的所有资源都在锁定范围内(在示例中仅共享_messages )。

If newMessages was instead a List of some new ChatMessage object, what would I need to do to ensure that GetNewMessages won't cause thread-safety issues when returning the List of ChatMessage? 如果newMessages而是一些新的ChatMessage对象的列表,我该怎么做才能确保GetNewMessages在返回ChatMessage列表时不会引起线程安全问题?

No - your code in GetNewMessages would not have to change. 否-您无需更改GetNewMessages的代码。 However , ChatMessage must make its own guarantees about thread-safety. 但是ChatMessage必须对线程安全做出自己的保证。 In fact, it would be impossible to code GetNewMessages to provide thread-safety if ChatMessage itself was not thread safe (short of cloning the objects, of course). 实际上,如果ChatMessage本身不是线程安全的(当然,不克隆对象),就不可能对GetNewMessages进行编码以提供线程安全。

Suggestions : 意见建议

  1. Your code is broken at the moment: _servers and _lock are not initialised - please fix it up in your post so it's easier to copy & paste 您的代码目前已损坏: _servers_lock尚未初始化-请在您的帖子中对其进行修复,以便更轻松地复制和粘贴

  2. You do not need any locks in your ServerManager - assuming _servers is NEVER added to or removed from. 您不需要在ServerManager 任何锁-假定_servers 绝不会添加或删除。

  3. You may or may not need locks in your ChatServer class. 您可能需要也可能不需要ChatServer类中的锁。 As it's written now, there are no thread-safety issues with it (that is, your lock s are correct). 就像现在写的那样,它没有线程安全问题(也就是说,您的lock是正确的)。 However, depending on how the class is used, you may want to look into ReaderWriterLock or ConcurrentBag<T> 但是,根据类的使用方式,您可能需要查看ReaderWriterLockConcurrentBag<T>

Your ServerManager class can be written as follows without locks, and will be threadsafe: 您的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();
    }
}

A short summary of this pretty long answer: Your code is thread-safe, but it can be improved. 这个很长答案的简短摘要:您的代码是线程安全的,但可以对其进行改进。

Dictionary of ChatServer objects which is initialized and populated only once, and never added to or removed from again. ChatServer对象的字典,该字典仅被初始化和填充一次,并且永远不会添加或删除。

You don't need to synchronize access to the Dictionary if it's read-only. 如果Dictionary是只读的,则不需要同步对Dictionary访问。 However, if it is read-only, make it explicit in your code using ReadOnlyDictionary class. 但是,如果它是只读的,则使用ReadOnlyDictionary类在代码中将其明确显示。

Is it thread-safe to synchronize access to _servers when getting the reference to the requested object, but then utilize that reference (currentServer) outside of the lock block? 获取对所请求对象的引用,然后在锁块之外利用该引用(currentServer),同步对_servers的访问是否具有线程安全性? In other words, can multiple threads safely access different elements within the Dictionary as long as access shared data within these objects is synchronized? 换句话说,只要同步访问这些对象中的共享数据,多个线程是否可以安全地访问Dictionary中的不同元素?

It is thread-safe, but it would be better to use a specialized data structure that would do that for you. 它是线程安全的,但是最好使用专门的数据结构来为您做到这一点。 For example ConcurrentDictionary . 例如ConcurrentDictionary The thread-safe data structures are usually much more efficient as most of them uses Interlocked for thread sync. 线程安全的数据结构通常效率更高,因为其中大多数将Interlocked用于线程同步。

Your dictionary is read-only, so it doesn't even need to be synchronized. 您的字典是只读的,因此甚至不需要同步。

Is it thread-safe to return the List of String to the caller as I do in ChatServer.GetNewMessages()? 像在ChatServer.GetNewMessages()中一样,将字符串列表返回给调用方是否是线程安全的?

It's a brand new list for every call, it is thread-safe. 这是每次调用的全新列表,它是线程安全的。 But why do you return List<string> ? 但是为什么返回List<string> Should the clients be able to modify the returned collection ( Add , Remove , Clear )? 客户应该能够修改返回的集合( AddRemoveClear )吗? Choose a minimal interface the clients will need, like IEnumerable<string> or IReadOnlyList<string> and use List<T>.AsReadOnly() to return it. 选择客户端所需的最小接口,例如IEnumerable<string>IReadOnlyList<string>然后使用List<T>.AsReadOnly()返回它。 You still need a copy of the list, AsReadOnly() only creates a wrapper around the list, not a read-only copy. 您仍然需要列表的副本, AsReadOnly()仅在列表周围创建包装器,而不是只读副本。

An even better option is to store messages in a thread-safe collection, for example ConcurrentQueue<T> . 更好的选择是将消息存储在线程安全的集合中,例如ConcurrentQueue<T> You could use the queue directly in your clients, or return a snapshot every time GetNewMessages is called using ConcurrentQueue<T>.ToArray() . 您可以直接在客户端中使用队列,也可以在每次使用ConcurrentQueue<T>.ToArray()调用GetNewMessages返回快照。

Here you can see a great thing about making your methods return interfaces. 在这里,您可以看到关于使方法返回接口的一件很棒的事情。 The first option actually returns a ReadOnlyCollection<string> , the 2nd on string[] , but as both of these types implement IEnumerable<string> and IReadOnlyList<string> , the clients are not affected by your choice. 第一个选项实际上返回一个ReadOnlyCollection<string> ,第二个返回string[] ,但是由于这两种类型都实现IEnumerable<string>IReadOnlyList<string> ,因此客户端不受您的选择的影响。 It's an implementation detail you can change any time. 这是一个实现细节,您可以随时更改。

If newMessages was instead a List of some new ChatMessage object, what would I need to do to ensure that GetNewMessages won't cause thread-safety issues when returning the List of ChatMessage? 如果newMessages而是一些新的ChatMessage对象的列表,我该怎么做才能确保GetNewMessages在返回ChatMessage列表时不会引起线程安全问题?

Make ChatMessage immutable. 使ChatMessage不可变。 Immutable types are always thread-safe. 不可变类型始终是线程安全的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM