如果您只更改值,那么List<T>是否线程安全?

4
如果我有一个包含100项的列表,并且有4个线程每个线程修改25个项目,以便它们不会相互影响,那么这个列表是否是“线程安全”的,即是否能够按预期工作?
例如,您拥有一个包含1000只猫的数组,它们的“名称”属性为空,因为它们尚未被命名。您想浏览并对它们进行命名,但您希望它是多线程的。因此,您创建10个线程并告诉每个线程仅处理0-99、100-199等内容,并将它们启动。

1
是的。请考虑到创建和启动线程所需的时间已经比更改元素要多。你并不领先。 - Hans Passant
Parallel.For()会为您进行分区。虽然没有单独线程的成本,但是仅用于切换1个属性仍然不会带来太多收益。 - H H
4个回答

2
这将有效,因为你只是从列表中读取,而不是向其中写入。

你的意思是,我不对列表本身进行编写,只针对它所引用的对象进行编写,并且永远不会同时进行? - NibblyPig
将数据写入列表元素,而不是修改列表本身的结构。 - sq33G

2
正如其他人已经指出的那样,阅读的行为本质上是线程安全的。只有当某些线程想要写入和/或其他线程想要从资源中读取时,并发才成为一个问题。
话虽如此,我可以补充说,如果您使用集合来实现这个目的,则可以考虑使用显式的只读集合(或仅使用IEnumerable)。.NET现在提供了这些只读集合。这使得更明确地表明您只从集合中读取。
顺便说一下:我不知道您的需求背景,也许您在这里过度简化了问题,但是序列访问内存中的集合非常快,因此您有多个线程从集合中读取只有以下情况才是合理的:
  • 您不是从内存而是从磁盘中读取(I/O很慢)
  • 您执行操作以操作对象很慢(例如毫秒级或更长时间,而不是简单的属性修改)

阅读不需要线程安全,除非有文档说明。例如,考虑一个MRU缓存的字典。 - Eric Lippert
Eric,这取决于您如何定义“读取”。在MRU缓存的情况下,读取还意味着对寄存器进行内部写入,以表示该项刚刚被读取,从而导致(隐藏的)副作用。这就是默认情况下它不是线程安全的原因。话虽如此,在调用似乎是“读取方法”的任何编程上下文中,我同意您不能盲目地相信任何实例都是线程安全的,因此请确保文档告诉您它是线程安全的,或者您非常确定它是线程安全的(例如List<T>)。 :) - Wim.van.Gool

1
如果您没有修改列表本身(添加或删除项目),并且可以保证4个线程仅使用自己的对象,则从线程同步的角度来看,您将不会有问题。

0

你可以使用并行 Linq 特性来代替一些自制的线程:

namespace CSharp
{
  using System;
  using System.Collections.Generic;
  using System.Linq;

  class Cat
  {
    public string Name { get; set; }
  }

  class Program
  {
    private static void Main()
    {
      var cats = new List<Cat>();

      for (int i = 0; i < 100; ++i)
      {
        cats.Add(new Cat());
      }

      cats.AsParallel().ForAll(cat => cat.Name = "Carlo");

      foreach (var cat in cats)
      {
        Console.WriteLine(cat.Name);
      }

      Console.ReadLine();
    }
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接