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 が意味したのは、長さの計算後に配列の内容が変更される可能性がある場合、文字列の合計長の計算が異なる結果につながる可能性があるということです。連結操作はアンマネージ コードで行われるため、後で文字列の長さが変更された場合、バッファー オーバーランが発生する可能性があります。

ここでの問題を説明するために、彼らが話していた問題の再現を示します。

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 に答える