C#数组是否安全?

Gar*_*ary 51 c# arrays multithreading thread-safety

特别是

  1. 创建一个函数以将数组和索引作为参数.
  2. 创建一个元素数组.
  3. 创建一个计数循环.
  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);
    }
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*eet 44

我相信如果每个线程只在阵列的一个单独部分上工作,那么一切都会很好.如果您要共享数据(即在线程之间进行通信),那么您将需要某种内存屏障来避免内存模型问题.

相信如果你产生了一堆线程,每个线程都会填充它自己的数组部分,然后等待所有这些线程完成使用Thread.Join,这样做就足以保证你的安全.我暂时没有任何支持文件,请注意......

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

  • @Rik:不,这不一样.线程安全几乎不应简化为"是"或"否"答案. (8认同)
  • 数组元素遵循与常规变量相同的规则,因为交换引用将是原子的,但通常不会替换结构.但是,这并没有考虑到需要内存屏障来使访问变得易失.正如你所说,它是否安全将完全取决于编码器正在做什么. (3认同)
  • @Steven"它是否安全将完全取决于编码人员在做什么." - 这不是说"不,这不安全".? (2认同)
  • 早期的英特尔处理器将这种策略用于多任务(8086) - 较低的640kb保留用于应用程序和操作系统,其余用于BIOS.后来,对虚拟地址范围的硬件支持(80286,保护模式)平息了"让我们在一个阵列上全部攻击"的狂野西部编码.如果您打算使用这种技术,请帮自己一个忙,并使用System.ArraySegment <T>. (2认同)

Jor*_*ren 25

关于Arrays的MSDN文档说:

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

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

枚举通过集合本质上不是线程安全的过程.即使集合是同步的,其他线程仍然可以修改集合,这会导致枚举器抛出异常.为了在枚举期间保证线程安全,您可以在整个枚举期间锁定集合,也可以捕获由其他线程所做的更改导致的异常.

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

  • 这不回答我的问题.我没有做任何列举这个家伙怎么得到13票? (17认同)
  • 这个答案真的没有用,因为MSDN的引用非常误导*和*没有关键.对引用类型的数组访问是原子的; 甚至枚举这样一个数组(虽然可能是不明智的)就是在线程安全的程度. (6认同)
  • 标题问题是"C#数组是否安全线程".我的答案的第一行阐明了静态的线程安全性和实例成员的安全性缺乏.因此,数组不是线程安全的.任何其他操作都不是关于数组的线程安全性,而是关于存储数组的线程安全性. (5认同)
  • 投票意味着答案是有帮助的.您的复选标记表示答案可以解决您的问题.鉴于这些信息,如果我正在使用Arrays开发一个可线程类,我相信我要做的第一件事是启动Reflector,打开一个在线程安全上下文中使用Array的Framework类,看看他们在做什么使用SyncRoot. (4认同)
  • @Abel:考虑到我在OP进行编辑之前回答了问题,该编辑指定了他如何访问共享内存,我只能根据问题标题做出一般性回答.通常,数组不是线程安全的.当我写这篇文章时,这可能是我的思路.经过4.5年的回顾,我会说我的答案不是很好,就像你说的那样,错过了问题的重点(当我回答这个问题时,这一点还很不清楚),但是,我真的不想放入当问题已经收到更好的答案而问题已经过时时,努力解决问题. (3认同)

Dan*_*iel 14

通常,当一个集合被称为"非线程安全"时,这意味着并发访问可能在内部失败(例如,当另一个线程在列表的末尾添加一个元素时,读取List <T>的第一个元素是不安全的:列表<T>可能会调整基础数组的大小,并且在将数据复制到新数组之前,读访问可能会转到新数组.

数组不可能出现这样的错误,因为数组是固定大小的,没有这种"结构变化".具有三个元素的数组与三个变量相比没有或多或少地具有线程安全性.

C#规范没有说明这一点; 但很明显,如果您了解IL并阅读CLI规范 - 您可以获得一个托管引用(如用于C#"ref"参数的那些)到数组内的元素,然后执行常规和易失性加载并存储到它.CLI规范描述了此类加载和存储的线程安全保证(例如元素的原子性<= 32位)

因此,如果我正确地解释您的问题,您想要使用不同的线程填充数组,但是只会为每个数组元素分配一次?如果是这样,那就完全是线程安全的.


Eri*_*ric 5

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

这个for循环:

for (int i = 0; i < 100; i++) { 
  a[i] = a[i]*a[i]; 
}
Run Code Online (Sandbox Code Playgroud)

Parallel.For(0, 100, delegate(int i) { 
  a[i] = a[i]*a[i]; 
});
Run Code Online (Sandbox Code Playgroud)

所以,是的,你的例子应该没​​问题.这是一篇关于C#中新的并行支持的博客文章.