简体   繁体   English

C#多线程列表操作

[英]C# multithreaded list operations

If I have something like this (pseudocode): 如果我有这样的东西(伪代码):

class A
{
    List<SomeClass> list;

    private void clearList()
    {
        list = new List<SomeClass>();
    }

    private void addElement()
    {
        list.Add(new SomeClass(...));
    }
}

is it possible that I run into multithreading problems (or any kind of unexpected behavior) when both functions are executed in parallel? 当两个函数并行执行时,我是否可能遇到多线程问题(或任何类型的意外行为)?

The use case is a list of errors, which could be cleared at any time (by simply assigning a new, empty list). 用例是一个错误列表,可以随时清除(通过简单地分配一个新的空列表)。

EDIT: My assumptions are 编辑:我的假设是

  • only one thread adds elements 只有一个线程添加元素
  • forgotten elements are okay (ie race condition between clearing and adding a new element), as long as the clear operation succeeds without problems 被遗忘的元素是可以的(即清除和添加新元素之间的竞争条件),只要清除操作成功没有问题
  • .NET 2.0 .NET 2.0

There are two possibilities for problems here: 这里有两种可能的问题:

  • Newly added items could end up being forgotten immediately, because you clear out and create a new list. 新添加的项目最终可能会立即被遗忘,因为您清除并创建新列表。 Is that an issue? 这是一个问题吗? Basically, if AddElement and ClearList are called at the same time, you have a race condition: either the element will end up in the new list, or in the old (forgotten) one. 基本上,如果同时调用AddElementClearList ,则会出现竞争条件:元素将在新列表中结束,或者在旧的(遗忘的)列表中结束。
  • List<T> isn't safe for multi-threaded mutation, so if two different threads call AddElement at the same time the results aren't guaranteed List<T>对于多线程变异是不安全的,因此如果两个不同的线程同时调用AddElement ,则不能保证结果

Given that you're accessing a shared resource, I would personally hold a lock while accessing it. 鉴于您正在访问共享资源,我会在访问时亲自握住它。 You'll still need to consider the possibility of clearing the list immediately before/after adding an item though. 您仍然需要考虑在添加项目之前/之后立即清除列表的可能性。

EDIT: My comment about it being okay if you're only adding from one thread was already somewhat dubious, for two reasons: 编辑:我的评论是好的,如果你只是从一个线程添加已经有点可疑,原因有两个:

  • It's possible (I think!) that you could end up trying to add to a List<T> which hadn't been fully constructed yet. 有可能(我认为!)你最终可能会尝试添加到尚未完全构建的List<T> I'm not sure, and the .NET 2.0 memory model (as opposed to the one in the ECMA specification) may be strong enough to avoid that, but it's tricky to say. 我不确定,.NET 2.0内存模型(与ECMA规范中的模型相反)可能足以避免这种情况,但说它很棘手。
  • It's possible that the adding thread wouldn't "see" the change to the list variable immediately, and still add to the old list. 添加线程可能不会立即“看到”对list变量的更改,仍然会添加到旧列表中。 Indeed, without any synchronization, it could see the old value forever 实际上,没有任何同步,它可以永远看到旧的价值

When you add "iterating in the GUI" into the mix it gets really tricky - because you can't change the list while you're iterating. 当您在混合中添加“迭代”时,它变得非常棘手 - 因为您在迭代时无法更改列表。 The simplest solution to this is probably to provide a method which returns a copy of the list, and the UI can safely iterate over that: 最简单的解决方案可能是提供一个返回列表副本的方法,UI可以安全地迭代:

class A
{
    private List<SomeClass> list;
    private readonly object listLock = new object();

    private void ClearList()
    {
        lock (listLock)
        {
            list = new List<SomeClass>();
        }
    }

    private void AddElement()
    {
        lock (listLock)
        {
            list.Add(new SomeClass(...));
        }
    }

    private List<SomeClass> CopyList()
    {
        lock (listLock)
        {
            return new List<SomeClass>(list);
        }
    }

}

Yes - it is possible,. 对的,这是可能的,。 In fact, if these are genuinely being called at the same time, it is highly likely. 事实上,如果真的同时被召唤,那很有可能。

In addition, it is also likely to cause problems if two seperate calls to addElement occur at the same time. 此外,如果同时发生对addElement的两次单独调用,也可能会导致问题。

For this sort of multithreading, you really need some sort of mutually exclusive lock around the list itself, so only one operation on the underlying list can be called at a time. 对于这种多线程,您确实需要在列表本身周围进行某种互斥锁定,因此一次只能调用基础列表上的一个操作。

A crude locking strategy around this would help. 围绕这一点的粗略锁定策略将有所帮助。 Something like: 就像是:

class A
{
    static object myLock = new object()
    List<SomeClass> list;

    private void clearList()
    {
        lock(myLock)
        {
          list = new List<SomeClass>();
        }

    }

    private void addElement()
    {
        lock(myLock)
        {
          list.Add(new SomeClass(...));
        }
    }
}

Collections in .NET (up to 3.5) are not thread-safe or non-blocking (parallel execution). .NET中的集合(最多3.5个)不是线程安全的或非阻塞的(并行执行)。 You should implement yours by deriving from IList and use a ReaderWriterLockSlim for performing every action. 您应该通过从IList派生并使用ReaderWriterLockSlim来执行每个操作来实现您的。 For example, your Add method should look like this: 例如,您的Add方法应如下所示:

    public void Add(T item)
    {
        _readerWriterLockSlim.EnterWriteLock();
        try { _actualList.Add(item); }
        finally { _readerWriterLockSlim.ExitWriteLock(); }
    }

You must be aware of some concurrency tricks here. 您必须了解一些并发技巧。 For example you must have a GetEnumerator which returns a new instance as an IList; 例如,您必须有一个GetEnumerator,它将一个新实例作为IList返回; not the actual list. 不是实际的清单。 Otherwise you will run into problems; 否则你会遇到问题; which should look like: 应该是这样的:

    public IEnumerator<T> GetEnumerator()
    {
        List<T> localList;

        _lock.EnterReadLock();
        try { localList= new List<T>(_actualList); }
        finally { _lock.ExitReadLock(); }

        foreach (T item in localList) yield return item;
    }

and: 和:

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return ((IEnumerable<T>)this).GetEnumerator();
    }

Note: When implementing thread-safe or parallel collections (and in fact every other class) DO NOT DERIVE FROM THE CLASS, BUT INTERFACE! 注意:当实现线程安全或并行集合(实际上是每个其他类)时,请不要从类中导出,而是实现接口! Because there will be always problems related to internal structure of that class or some methods that are not virtual and you have to hide them and so on. 因为总会出现与该类的内部结构相关的问题,或者某些非虚拟的方法,您必须隐藏它们等等。 If you have to do this, do it very carefully! 如果你必须这样做,请非常小心!

It is properly not a good thing to just make a new List when you want to clear it. 如果要清除它,只需创建一个新列表就不是一件好事。

I assume you also assigned list in the constructor so you don't run into a null-pointer exception. 我假设您还在构造函数中分配了列表,因此您不会遇到空指针异常。

If you clear and elements is added, they can be added to the old list which I assume is fine? 如果你清除并添加了元素,它们可以添加到我认为没问题的旧列表中吗? BUT if two elements is added at the same time, you can run into problems. 但如果同时添加两个元素,则可能会遇到问题。

Look into .Net 4 new collections to handle multithreading tasks :) 查看.Net 4新集合来处理多线程任务:)

ADDITION: Look into the namespace System.Collections.Concurrent if you use .Net 4. There you will find: System.Collections.Concurrent.ConcurrentBag<T> and many other nice collections :) 附加:如果你使用.Net 4,请查看命名空间System.Collections.Concurrent。你会发现: System.Collections.Concurrent.ConcurrentBag<T>和许多其他好的集合:)

You should also note that lock can significantly pull down performance if you dont watch out. 您还应注意,如果您不注意,锁定可以显着降低性能。

If you use one instance of this class in multiple threads, yes. 如果在多个线程中使用此类的一个实例,是的。 you will run into problems. 你会遇到问题。 All collections in the .Net framework (version 3.5 and lower) are NOT thread-safe. .Net框架(3.5及更低版本)中的所有集合都不是线程安全的。 Specially when you start changing the collection while another thread is itterating over it. 特别是当你开始更改集合而另一个线程正在对它进行迭代时。

Use locking and give out ´copies of´ collections in multithreaded environments, or if you can use .Net 4.0, use the new concurrent collections. 在多线程环境中使用锁定并提供“复制”集合,或者如果可以使用.Net 4.0,则使用新的并发集合。

It is clear from the edits to your question that you do not really care about the usual culprits here - there are really no simultaneous calls to the methods of the same object. 从您对问题的编辑中可以清楚地看出,您并不真正关心通常的罪魁祸首 - 实际上并没有同时调用同一对象的方法。

Essentially you are asking if it is ok to assign the reference to your list while it is being accessed from a parallel thread. 基本上,您在询问是否可以在从并行线程访问列表时为其分配引用。

As far as I understand it still can cause trouble. 据我了解,它仍然会造成麻烦。 It all depends on how reference assignment is implemented on the hardware level. 这完全取决于如何在硬件级别上实现引用分配。 To be more precise whether this operation is atomic or not. 更准确地说,此操作是否是原子操作。

I think that as slim as it is there is still a chance, especially in multiprocessor environments, that the process will get corrupted reference because it was only partially updated when it was accessing it. 我认为,尽管存在很小的问题,但仍有机会,特别是在多处理器环境中,该进程将被损坏的引用,因为它只是在访问它时才进行了部分更新。

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

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