String.Concat和线程安全?

时间:2013-01-12 09:45:52

标签: c# .net string multithreading

sking this question之后,我仍然有一个问题。

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

我将从一个例子开始:

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变量!
 (请记住,我无法向arrray添加项目。因为创建时会创建数组长度)

无论我如何处理函数 Do - (无论在什么线程中),Console.Writeline结果始终为3(除非使用{ {1}})。

让我们看看真实的代码:

ref

我的意思是:一旦我在#{1}}的线程#1上,这个线程将永远有长度为3的数组。

如果另一个线程想要破坏/更改数组长度(我无法理解如何,导致数组具有固定长度,他所能做的只是做它{ {1}}) - 它将有一个不同的指针地址副本。

我必须在这里遗漏一些东西。

  • 我错过了什么?

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

5 个答案:

答案 0 :(得分:6)

我在最初的帖子中找到了Jon Skeet和Eric Lippert的答案,我相信你错误地解释了他们的答案。他们担心如果你不将字符串复制到新数组,有人会在{"1","2","3"}首次评估输入并执行之前将{"a very very", "long string", "(but much longer, yes?"}更改为ConcatString。串联。

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

答案 1 :(得分:4)

  

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

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

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

答案 2 :(得分:2)

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

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

答案 3 :(得分:1)

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

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

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

答案 4 :(得分:1)

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

为了说明这里的问题是他们所讨论的问题的重演:

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;
        }



    }
}