在多个线程之间共享列表<t> </t>

时间:2013-01-14 15:51:48

标签: c# multithreading locking

我是否正确地说我只需要使用锁来添加/删除/更改列表,或者我还需要在迭代时锁定它?

所以我通过这样做是安全的:

class ItemsList
{
    List<int> items = new List<int>();
    object listLock = new object();

    public void Add(int item)
    {
        lock (listLock)
        {
            items.Add(item);
        }
    }

    public void Remove(int item)
    {
        lock (listLock)
        {
            items.Remove(item);
        }
    }

    public void IncrementAll()
    {
        foreach (var item in items)
        {
            item += 1;
        }
    }
}

7 个答案:

答案 0 :(得分:5)

当迭代它时你肯定会锁定 - 如果在迭代它时更改列表,则会抛出异常。

来自List<T>.GetEnumerator的文档:

  

枚举器没有对集合的独占访问权限;因此,枚举通过集合本质上不是一个线程安全的过程。为了在枚举期间保证线程安全,您可以在整个枚举期间锁定集合。要允许多个线程访问集合以进行读写,您必须实现自己的同步。

此外,即使是List<T>的单个读取也不是线程安全的,如果您也可以写入它 - 即使它没有失败,也无法保证您获得最多最近的价值。

基本上,List<T> 对于多个线程是安全的,如果在所有线程的状态变得可见的最后一个点之后没有写入它。

如果您想要一个线程安全的集合,并且如果您使用的是.NET 4或更高版本,请查看System.Collections.Concurrent命名空间。

答案 1 :(得分:1)

List<T>通常不是线程安全的。拥有多个阅读器不会导致任何问题,但是,在阅读时您无法写入列表。因此,您需要锁定读取和写入,或使用类似System.Threading.ReaderWriterLock(允许多个读取器,但只允许一个编写器)。如果您是在.NET 4.0 or bigger下开发,则可以使用BlockingCollection代替,这是一个线程安全的集合。

答案 2 :(得分:0)

不,那不安全。如果另一个线程在您阅读时修改了它,您将获得“集合被修改”的异常类型。

解决此问题的最有效方法是使用ReaderWriterLockSlim来控制访问权限,以便多个线程可以同时读取它,并且只有在尝试修改它时才会被锁定。

答案 3 :(得分:0)

You're not even thread safe with what you have if you never iterate it

在我们讨论它是否能按预期工作之前,您需要定义您正在对数据结构执行的操作类型。

在一般情况下,您确实需要在阅读时锁定。实际上,有人可以在你进行迭代的过程中添加一个项目,它会破坏各种各样的东西。如果您在阅读过程中添加了一个项目,即使阅读单个项目也可能会被破坏。

另请注意,这最多会使每个操作在逻辑上都是原子的。如果您正在执行多个操作并对数据结构的状态做出假设,那么这还不够。

在许多情况下,要解决此问题,您需要在调用者端进行锁定,而不是仅将每个操作包装在lock中。

答案 4 :(得分:0)

您应该使用ReaderWriterLockSlim,以便多个线程可以读取集合,但只有一个可以修改它。

答案 5 :(得分:0)

您可能需要查看ConcurrentQueue&lt;&gt;();

这基本上是一个线程安全列表(据我所知),这是相当方便的。你可以使用它有点像这样;

   public ConcurrentQueue<yourType> alarmQueue = new ConcurrentQueue<yourType>();
    System.Timers.Timer timer;


    public QueueManager()
    {
        timer = new System.Timers.Timer(1000);
        timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
        timer.Enabled = true;
    }

    void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        DeQueueAlarm();
    }

    private void DeQueueAlarm()
    {
        yourType yourtype;
        while (alarmQueue.TryDequeue(out yourtype))
        {
           //dostuff
        }

    }

编辑:正如约翰所说,这可以在.Net4开始使用。在这里阅读更多; http://msdn.microsoft.com/en-us/library/dd267265.aspx

答案 6 :(得分:0)

在IncrementAll上,您将捕获InvalidOperationException,因为在集合中进行了更改。您可以在测试单元中看到它,如下所示:

        ItemsList il = new ItemsList();
        Task ts = new Task(() =>
        {
            for (int i = 0; i < 100000; i++)
            {
                il.Add(i);
                System.Threading.Thread.Sleep(100);

            }
        }
        );
        ts.Start();
        Task ts2 = new Task(() =>
        {
            //DoSomeActivity
            il.IncrementAll();
        }
        );
        ts2.Start();
        Console.Read();

迭代也必须锁定!!!