0

最近、lockステートメントの例を書こうとしました。次のコードを検討してください。

public partial class Form1 : Form
    {
        private class Concurrency
        {
            private int _myValue;
            private object _locker = new object();
            public int Value
            {
                set
                {
                    lock (_locker)
                    {
                        _myValue = value;
                        Thread.Sleep(new Random().Next(5, 25));
                    }
                }

                get
                {
                    return _myValue;
                }
            }
        }

        private Random _random;
        private Concurrency _concurrency;

        public Form1()
        {
            InitializeComponent();
            _random = new Random();
            _concurrency = new Concurrency();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            CreateTask(1);
            CreateTask(2);
        }

        private void CreateTask(int taskId)
        {
            Task.Factory.StartNew(() =>
                {
                    for (int i = 0; i < 10; ++i)
                    {
                        int randomNumber = _random.Next(0, 50);

                        Console.WriteLine("Thread {0}, setting value {1}", taskId, randomNumber);
                        _concurrency.Value = randomNumber;
                        Console.WriteLine("Thread {0}, getting value {1}", taskId, _concurrency.Value);

                        Thread.Sleep(_random.Next(5, 15));
                    }
                });
        }
    }

結果は次のとおりです。

Thread 2, setting value 4
Thread 1, setting value 22
Thread 2, getting value 22
Thread 1, getting value 22
Thread 2, setting value 11
Thread 2, getting value 11
Thread 1, setting value 8
Thread 2, setting value 41
Thread 1, getting value 8
Thread 1, setting value 30
Thread 2, getting value 41
Thread 1, getting value 30
Thread 2, setting value 18
Thread 1, setting value 42
Thread 2, getting value 18
Thread 2, setting value 30
Thread 1, getting value 42
Thread 1, setting value 24
Thread 2, getting value 30
Thread 1, getting value 24
Thread 2, setting value 13
Thread 1, setting value 7
Thread 2, getting value 13
Thread 2, setting value 13
Thread 1, getting value 7
Thread 2, getting value 13
Thread 1, setting value 38
Thread 2, setting value 19
Thread 1, getting value 38
Thread 1, setting value 4
Thread 2, getting value 19
Thread 2, setting value 44
Thread 1, getting value 4
Thread 2, getting value 44
Thread 1, setting value 48
Thread 2, setting value 12
Thread 1, getting value 48
Thread 1, setting value 47
Thread 2, getting value 12
Thread 1, getting value 47

ご覧のとおり、最初の設定/取得の状況を除いて、すべてが正常です。スレッド2は値2を設定しますが、22を取得します。これは単一のケースではなく、毎回発生します。設定と取得はアトミックではなく、タスクの命令の周りにロックを設定する必要があることは知っていますが、最初の試行が常に失敗し、他の試行が正常に機能するのはなぜですか?

編集:

Concurrencyクラスを次のように更新しました:

private class Concurrency
        {
            private static Random _random = new Random();
            private int _myValue;
            private object _locker = new object();
            public int Value
            {
                set
                {
                    lock (_locker)
                    {
                        _myValue = value;
                        Thread.Sleep(_random.Next(5, 250));
                    }
                }

                get
                {
                    return _myValue;
                }
            }
        }

Thread.Sleepでも時間範囲を拡張したことに注意してください。結果は次のとおりです。

Thread 2, setting value 3
Thread 1, setting value 9
Thread 2, getting value 9
Thread 2, setting value 44
Thread 1, getting value 9
Thread 1, setting value 35
Thread 2, getting value 44
Thread 2, setting value 32
Thread 1, getting value 35
Thread 1, setting value 25
Thread 2, getting value 32
Thread 2, setting value 15
Thread 1, getting value 25
Thread 1, setting value 5
Thread 2, getting value 15
Thread 2, setting value 34
Thread 1, getting value 5
Thread 1, setting value 42
Thread 2, getting value 34
Thread 2, setting value 36
Thread 1, getting value 42
Thread 1, setting value 8
Thread 2, getting value 36
Thread 2, setting value 42
Thread 1, getting value 8
Thread 1, setting value 16
Thread 2, getting value 42
Thread 2, setting value 0
Thread 1, getting value 16
Thread 1, setting value 43
Thread 2, getting value 0
Thread 2, setting value 20
Thread 1, getting value 43
Thread 1, setting value 30
Thread 2, getting value 20
Thread 2, setting value 38
Thread 1, getting value 30
Thread 1, setting value 0
Thread 2, getting value 38
Thread 1, getting value 0

何も変わっていません。ランダムではなく、他のことだと思います。

4

2 に答える 2

1

最初の1つだけでなく、何度も発生します。

あなたはそれを一度だけ「見る」のですが、実際はそれがあなたのプログラムのバグです。潜在的に、2つの「設定...」が表示されるたびに、最後の設定を読むことができます。この状況を想像してみてください。

メインスレッド1スレッド2
値=0         
intx1=値     
                  値=2
                  intx2=値
WriteLine(x1)     
                  WriteLine(x2)

出力は正しいです(スレッド1の場合は0、スレッド2の場合は2)。ここで、スケジューリングが次のようになっていると想像してください。

メインスレッド1スレッド2
値=0         
                  値=2
intx1=値     
WriteLine(x1)     
                  intx2=値
                  WriteLine(x2)

両方のスレッドで値2を読み取るため、間違った結果が得られます。実際には、ロックされた操作はセットのみであるため、間違いではありません。スレッド1の読み取り操作(プロパティ値の取得)が保証されるわけではありません。スレッド2の書き込み操作(プロパティ値の設定)の前に実行されます。

最後に、この投稿も見てください。これを書くと、そのようなコードが(まったく同じ理由で)失敗する可能性があることがわかります。

++_concurrency.Value;
于 2013-03-04T12:44:41.167 に答える
-1

ご指摘のとおり、ロックは正しくありません。ですから、「最初を除いて、なぜそれが機能しているように見えるのか」という問題です。(私はあなたの質問を言い換えているだけです。)

[編集]

私が話していた問題を削除するためにコードを変更したので、ここに別のアイデアがあります-それは本当に答えだと思います。

コードの作成方法では、スレッドがロックを終了してから値を読み戻すまでの期間は非常に短いです。

セッターを調べます。

set
{
    lock (_locker)
    {
        _myValue = value;
        Thread.Sleep(_random.Next(5, 25));
    }
}

これで、thread1がロック内にある場合、それは_myValueを設定してからスリープします。その間、Thread2はロックに入るのを待って座っています。

thread1がスリープを終了すると、すぐにロックを終了し、次のコード行(この場合は現在の値を出力する行)に進みます。

Console.WriteLine("Thread {0}, getting value {1}", taskId, _concurrency.Value);

ロックを終了してから間接参照するまでの間にthread1のスケジュールが解除されない限り、スレッド1_concurrency.Valueは正しい値を受け取ります。時間が非常に短いため、その期間中にスケジュールが解除される可能性はほとんどありません。

thread1のスケジュールが解除された場合 thread2はロックに入り、_myValuethread1がそれを参照解除する前に変更できます。

スレッド設定から値を取得するまでの時間を長くするために何かを行うと、「誤った」値が観察される可能性が高くなります。

次のプログラムを試してから、。で示された行のコメントを解除してください// Try with this sleep uncommented.。「NumberMismatch」を印刷する行がさらに多く表示されます。

using System;
using System.Threading;
using System.Threading.Tasks;


namespace Demo
{
    class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine("Starting");
            CreateTask(1);
            CreateTask(2);
            Console.ReadKey();
        }

        private static void CreateTask(int taskId)
        {
            Task.Factory.StartNew(() =>
            {
                for (int i = 0; i < 10; ++i)
                {
                    int randomNumber = _random.Next(0, 50);

                    Console.WriteLine("Thread {0}, setting value {1}", taskId, randomNumber);
                    _concurrency.Value = randomNumber;
                    // Thread.Sleep(10); // Try with this sleep uncommented.
                    int test = _concurrency.Value;
                    Console.WriteLine("Thread {0}, getting value {1}", taskId, test);

                    if (test != randomNumber)
                    {
                        Console.WriteLine("Number mismatch.");
                    }

                    Thread.Sleep(_random.Next(5, 15));
                }
            });
        }

        private static Random _random = new Random();
        private static Concurrency _concurrency = new Concurrency();

    }

    class Concurrency
    {
        private int _myValue;
        private object _locker = new object();
        public int Value
        {
            set
            {
                lock (_locker)
                {
                    _myValue = value;
                    Thread.Sleep(_random.Next(5, 25));
                }
            }

            get
            {
                return _myValue;
            }
        }

        static Random _random = new Random();
    }
}

では、なぜ最初は失敗するのでしょうか。まあ、それはスレッドがシステムによって開始される方法の単なるアーティファクトだと思います。

于 2013-03-04T12:51:29.720 に答える