C# Parallel.For和ConcurrentBag提供不可预测的行为

C# Parallel.For和ConcurrentBag提供不可预测的行为,c#,multithreading,C#,Multithreading,我正在编写一个应用程序,在某个时候,它会获取一个网格体并计算邻接索引。为此,我定义了一个ConcurrentBag对象数组,然后,在并行for循环中,我只需检查一些面,如果它们有任何邻接,我将索引添加到相应索引中的所述包中。即: private bool parallelize = true; private volatile ConcurrentBag<int>[] edge_adjacencies; if (parallelize) { ... Parallel.

我正在编写一个应用程序,在某个时候,它会获取一个网格体并计算邻接索引。为此,我定义了一个ConcurrentBag对象数组,然后,在并行for循环中,我只需检查一些面,如果它们有任何邻接,我将索引添加到相应索引中的所述包中。即:

private bool parallelize = true;
private volatile ConcurrentBag<int>[] edge_adjacencies;
if (parallelize)
{
    ...
    Parallel.For(0, face_count, compute_adjacency_single);
    ...
}

private void compute_adjacency_single(int cur_idx)
{
    edge_adjacencies[cur_idx] = new ConcurrentBag<int>();
    foreach(int test_idx in SOME_TEST_SPACE)
    {
        if (test_idx != cur_idx)
        {
            bool edge_adj, vertex_adj;
            get_adjacency(cur_idx, test_idx, out edge_adj, out vertex_adj);
            if (edge_adj && !collection_contains(edge_adjacencies[cur_idx], test_idx))
            {
                edge_adjacencies[cur_idx].Add(test_idx);
            }
        }
    }
}
运行5:

incorrect:2855
incorrect:2856
incorrect:2879
incorrect:2880
运行8:

incorrect:3271
大约每运行9次,就会给出不正确的结果

作为参考,当我以串行方式运行时,它每次都能完美地工作

我阅读了MS文档,它确实说System.collections.Concurrent中的集合应该是线程安全的,但看起来情况并非如此


为什么会发生这种情况,有没有一种很好的方法来防止这种情况发生?

嗯。这是一个猜测——但我不认为在
计算邻接中这样做是线程安全的

CuncurrentBag
是非常线程安全的,但是保存它们的实例的数组却不是


我会使用一个而不是数组。

我会使用一个锯齿状数组来确定(或者如果你不觉得很难实现的话,使用一个扁平的1D数组),但这只是我的偏好,因为我得到的数据或多或少是按索引排序的


我认为问题在于使用
bool-edge_-adj,vertex_-adj时存在竞争条件。创建一个数组或任何您更喜欢的线程安全数据结构,并使用
get_adjacence
函数填充它,这样您就可以访问布尔值,而不会让线程覆盖彼此的值。

我来看看是否可以使用字典实现,尽管每个线程都从该数组中访问一个唯一的元素……它必须是线程安全的集合类型,“普通”指令也会有同样的问题。问题不在于
cur_idx
的唯一性,而在于(我怀疑)数组索引方法的实现。如果每个线程访问数组中的不同索引,则不应该存在线程问题。@Rotem这与您所说的问题不同。访问数组中的包-功能类似于get方法。get方法返回特定索引处的包。get方法不是线程安全的。这正是我所说的。假设没有线程访问数组中的同一索引,那么访问是安全的。如果数组中从来没有任何两个线程访问同一索引,为什么还要使用
ConcurrentBag
?有什么并发访问?如何实现
collection\u contains
?如果线程从不访问同一数组元素,它们将如何覆盖彼此的值?@Rotem,因为没有数组!线程试图同时访问
布尔边、顶点变量显示这可能吗?这些是局部变量。它们只存在于线程本地
foreach
循环中。因为它们是通过引用
get_adjacency
方法传递的,所以会覆盖相同的地址。但地址是局部变量。每个线程的地址都不同。甚至可能在每个线程中循环的不同迭代中。
incorrect:2855
incorrect:2856
incorrect:2879
incorrect:2880
incorrect:3271