0

解决了这个问题之后,我还有一个问题。

我得到了两个很好的答案,但我仍然无法找到这实际上是如何发生的:(故障情况怎么会发生):

我将从一个例子开始:

public void Do(string [] g)
{
   g=null;     //<========
}

void Main()
{
    var t=new string[3];
    t[0]="1";   t[1]="1";   t[2]="1";
    Do( t);
    Console.WriteLine ( t.Length);
}

注释行可以在不同的线程中执行,但每个线程都有自己的 g变量!
(请记住,我无法将项目添加到数组中。因为数组长度是在创建时创建的)

无论我将使用该函数 Do做什么- (无论在哪个线程中),Console.Writeline 结果将始终为3(除非使用ref)。

所以让我们看看真正的代码:

    public static string Concat(params string[] values)
  #1  {
  #2      if (values == null)
  #3       {
  #4            throw new ArgumentNullException("values");
  #5       }
  #6        int totalLength = 0;
  #7        string[] strArray = new string[values.Length];
  #8        for (int i = 0; i < values.Length; i++)
  #9          {
  #10            string str = values[i];
  #11            strArray[i] = (str == null) ? Empty : str;
  #12            totalLength += strArray[i].Length;
  #13            if (totalLength < 0)
  #14            {
  #15                throw new OutOfMemoryException();
  #16            }
  #17         }
  #18        return ConcatArray(strArray, totalLength);
  #19   }

我的说法是:一旦我在 #1 上,在线程中X,这个线程将永远拥有长度为 3 的数组。

如果另一个线程想要销毁/更改数组长度(我无法理解如何,因为数组具有固定长度,他所能做的就是制作它null)-它将有一个不同的指针地址副本。

我一定在这里遗漏了一些东西。

  • 我错过了什么?

  • 其他线程可以执行哪些代码会导致错误?(假设我们不复制数组)。

4

5 回答 5

6

我在原始线程中查看了 Jon Skeet 和 Eric Lippert 的答案,我相信你误解了他们的答案。他们担心,如果您不将字符串复制到新数组中,那么有人会出现并更改{"1","2","3"}{"a very very", "long string", "(but much longer, yes?"}ConcatString首次评估输入和执行连接之间。

请注意,第 12 行不处理输入数组的长度,而是处理最终输出字符串的长度。所有这些检查都处理连接的结果,而不是输入数组。

于 2013-01-12T10:24:27.493 回答
4

其他线程可以执行哪些会导致错误的代码是什么?

string[] data = { "1", "2", "3" };
ThreadPool.QueueUserWorkItem( () => { data[0] = "one"; } );
string total = String.Concat(data);

如果没有中间数组,就会出现竞争条件,线程可能会data[0] 其长度被添加到之后totalLength(可能是不安全的)调用之前发生变化ConcatArray()。然后,低级方法会将 5 个字符复制到大小为 3 的缓冲区。

于 2013-01-12T10:29:44.737 回答
2

数组引用被传递到方法中,因此每个线程(在这种情况下)都有自己的引用 g。每个线程都可以更改它自己的引用指向的内容(例如,g = null),但是当您访问 g 中的项目时,例如 g[0],两个线程将访问同一个项目。

所以,问题是g[0] = null,不是g = null

于 2013-01-12T10:00:41.610 回答
1

以下几行确保了数据一致性,以防数组内容在其他线程中被修改

  #11            strArray[i] = (str == null) ? Empty : str;
  #12            totalLength += strArray[i].Length;
  #18        return ConcatArray(strArray, totalLength);

比赛仍然存在,但在第号线之前。11,但是如果使用原始数组(以防数据损坏)错误的信息可能会传递给ConcatArray函数。

于 2013-01-12T10:02:50.443 回答
1

John Skeet 和 Eric Lippert 的意思是,如果在计算长度后数组内容可能发生变化,那么总字符串长度的计算会导致不同的结果。因为 concat 操作是在非托管代码缓冲区中完成的,所以如果之后的字符串长度确实发生了变化,则可能会发生溢出。

为了说明这个问题,这里复制了他们正在谈论的问题:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static string[] arr = new string[] { "1", "2", "3" };
        static string[] arr2 = new string[] { "1111", "2222222", "2222222222222222222222" };

        [MTAThread]
        public static void Main()
        {
            ThreadStart[] funcs = Enumerable.Range(0, Environment.ProcessorCount * 4).Select(i => new ThreadStart(MutateArray)).ToArray();
            foreach (var func in funcs)
                new Thread(func).Start();
            Console.ReadLine();
        }

        static void MutateArray()
        {
            Random rand = new Random();
            while (true)
            {
                int i = rand.Next(arr.Length);
                // swap array contents with contents from another array so arr will always
                // contain a mixture of arr and arr2 without knowing at which point of time which contents
                // are inside it.
                lock (arr)
                {
                    string tmp = arr[i];
                    arr[i] = arr2[i];
                    arr2[i] = tmp;
                }

                Do(arr); 

            }
        }

        static void Do(string[] g)
        {
            AllocateBufferWithRightLength(g, StrLen(g));
        }

        static void AllocateBufferWithRightLength(string[] g, int strLen)
        {
            int newLen = StrLen(g);
            if (strLen != newLen)
            {
                throw new Exception("Invariant broken");
            }
        }

        static int StrLen(string[] g)
        {
            int strLen = 0;
            foreach (var str in g)
            {
                if (str != null)
                    strLen += str.Length;
            }
            Thread.Sleep(1);
            return strLen;
        }



    }
}
于 2013-01-12T10:27:38.063 回答