2009-09-22 163 views
42

特别C#数组是否线程安全?

  1. 创建函数采取阵列和索引作为参数。
  2. 创建一个n元素数组。
  3. 创建一个n计数循环。
  4. 上一个新的线程在循环中分配对象的新实例,使用传入的索引数组。

我知道如何管理线程等。我很感兴趣,知道这是线程安全的做事方式。

class Program 
{ 
    // bogus object 
    class SomeObject 
    { 
     private int value1; 
     private int value2; 

     public SomeObject(int value1, int value2) 
     { 
      this.value1 = value1; 
      this.value2 = value2; 
     } 
    } 

    static void Main(string[] args) 
    { 

     var s = new SomeObject[10]; 
     var threads = Environment.ProcessorCount - 1; 
     var stp = new SmartThreadPool(1000, threads, threads); 
     for (var i = 0; i < 10; i++) 
     { 
      stp.QueueWorkItem(CreateElement, s, i); 
     } 

    } 

    static void CreateElement(SomeObject[] s, int index) 
    { 
     s[index] = new SomeObject(index, 2); 
    } 
} 
+20

这是完全没有问题的观点,但我会建议不要使用'Environment.ProcessorCount - 1' - 那些可怜的单核人“会是彻头彻尾的不快乐,否则...... – 2009-09-24 17:29:33

+6

还有人用单核心处理器? – Gary 2009-09-24 18:15:31

+4

@Gary有虚拟机,带有“一个”处理器; – IamIC 2011-12-29 10:18:26

回答

39

我相信如果每个线程只能在数组的单独部分工作,一切都会好的。如果你要共享数据(即在线程之间传递它),那么你需要某种内存屏障来避免内存模型问题。

相信,如果您产卵一堆线程,每个填充自己的数组的部分,然后等待所有这些线程的使用Thread.Join来完成,即会做足够的用于栅栏条款你是安全的。我目前没有任何支持文件,请注意你...

编辑:你的示例代码是安全的。任何时候都有两个线程访问同一个元素 - 就好像它们每个都有单独的变量。但是,这并不总是有用的。在某些情况下,线程通常会希望共享状态 - 一个线程会想要读取另一个线程写入的内容。否则,他们写入共享数组而不是写入他们自己的私有变量是没有意义的。 这就是你需要小心的地方 - 线程之间的协调。

+3

数组元素遵循与常规变量相同的规则,因为交换引用将是原子的,但替换结构通常不会。尽管如此,这并没有考虑到对内存屏障的需求,使访问变得不稳定。正如你所说的,是否安全取决于编码器正在做什么。 – 2009-09-22 15:18:02

+0

@Steven:是的,那是关于我的理解:) – 2009-09-22 15:23:42

+2

@Steven“是否安全将取决于编码器正在做什么。” - 和说“不,这不是安全的。” ? – Rik 2009-09-22 15:25:58

24

MSDN documentation on Arrays说:

公共静态(在Visual Basic中的Shared)这种类型的 成员都是线程安全的。 任何实例成员不是 保证是线程安全的。

此实现不提供 同步(线程安全)包装 数组;但是,基于Array的.NET Framework 类使用SyncRoot 属性提供了 自己的同步版本 集合。

枚举整个集合是 本质上不是线程安全的 过程。即使集合是 同步,其他线程仍然可以 修改集合,这将导致枚举器引发 引发异常。 要在枚举中保证线程安全,您可以在整个 枚举期间锁定 集合,或者捕获由其他 线程所做更改产生的异常 。

所以不,他们不是线程安全的。

+13

这不能回答我的问题。我没有进行任何列举,这家伙怎么能得到13票? – Gary 2009-09-22 16:16:28

+4

投票表示答案有帮助。您的选中标记表示答案可解决您的问题。考虑到这些信息,如果我正在开发一个带数组的线程化类,我相信我会做的第一件事就是触发Reflector,打开一个在线程安全的上下文中使用Array的Framework类,并查看它们在做什么与SyncRoot。 – 2009-09-22 18:59:20

+6

这个答案确实没有帮助,因为MSDN的报价很具误导性*和*不重要。对引用类型的数组访问是原子的;甚至列举出这样一个数组(尽管可能是不明智的)就是在线程安全的情况下。 – 2009-09-24 17:25:58

11

通常,当一个集合被认为是“不是线程安全的”,这意味着并发访问可能会在内部失败(例如,读取列表< T>的第一个元素并不安全,而另一个线程在列表:列表< T>可能调整底层数组的大小,并且在将数据复制到数组之前,读访问可能会转到新数组)。

这样的错误对于数组是不可能的,因为数组是固定大小的并且没有这样的“结构变化”。 具有三个元素的数组与三个变量相比不会更多或更少线程安全。

C#规范对此没有任何说明;但很明显,如果您知道IL并阅读CLI规范 - 您可以为数组内的元素获得托管引用(如用于C#“ref”参数的引用),然后执行正常和不稳定负载并存储到该元素。 CLI规范描述了这种加载和存储的线程安全保证(例如,元素的原子性< = 32位)

所以,如果我正确地解决了你的问题,你想用不同的线程填充一个数组,分配给每个数组元素只有一次?如果是这样,那完全是线程安全的。

4

您提供的示例与Microsoft自己的C#4.0并行扩展的工作方式非常相似。

for循环:

for (int i = 0; i < 100; i++) { 
    a[i] = a[i]*a[i]; 
} 

成为

Parallel.For(0, 100, delegate(int i) { 
    a[i] = a[i]*a[i]; 
}); 

所以,是的,你的榜样应该没问题。关于C#中新的并行支持,以下是旧版本blog post