2013-03-04 26 views
0

我最近试图写一个锁语句的例子。请考虑下面的代码:为什么一开始就有线程竞赛?

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而且它不是单一的情况下,它每次都发生。我知道设置和获取不是原子的,应该围绕任务中的指令设置锁定,但为什么第一次尝试总是失败并且其他工作正常?

编辑:

我更新并发类这样的:

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 

确实没有什么变化。我猜这不是Random问题,而是其他一些问题。

+0

尝试拉出for循环(从任务内部到button1_Click例程)... – 2013-03-04 13:10:19

回答

-1

正如您指出的,锁定不正确。所以这更多的是“为什么它似乎工作,除了一开始?”。 (我只是重申你的问题)

[编辑]

既然你改变了代码删除我在谈论这个问题,这里的另一个想法 - 我认为这真的是答案。

你有你的代码的方式,线程退出锁定和读取值之间有一段非常短的时间。

检查你的二传手:

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

现在,如果线程1是锁内,它将设置_myValue,然后睡觉。在此期间的线程2将坐在等待进入锁。

当线程1退出睡眠时,它立即离开锁并用的代码的下一行,在这种情况下是打印的电流值从线继续:

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

除非线程1是其间取消调度退出锁定并解除引用_concurrency.Value,它将收到正确的值。由于时间太短,在此期间不太可能不计划。

如果线程1 被取消预定,那么thread2将能够输入锁并在thread1取消引用之前更改_myValue

做任何事情来增加线程设置和获取值之间的时间将使得更可能观察到“不正确”的值。

请尝试下列程序,然后取消注释// Try with this sleep uncommented.指示的行。你会看到更多的行打印“数字不匹配”。

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

那么为什么一开始就会失败呢?那么,我认为这只是系统启动线程的一种人为因素。

+0

这两个线程之间共享随机数,不是吗? – Rawling 2013-03-04 12:54:50

+0

啊,我错过了......我看到了Form1类中的共享'_random',并且错过了他们创建_new_ randoms的位。 – Rawling 2013-03-04 12:58:51

+0

我更新了我的问题。 – 2013-03-04 13:09:03

1

它发生多次,而不仅仅是第一个

您“看到”它只是一次,而实际上错误在您的程序中。有可能每次你看到两个“设置......”你可能会读最后一个。想象一下这种情况:

 
Main Thread 1  Thread 2 
Value = 0   
int x1 = Value  
        Value = 2 
        int x2 = Value 
WriteLine(x1)  
        WriteLine(x2) 

输出正确(线程1为0,线程2为2)。现在想象一下,如果安排是这样的:

 
Main Thread 1  Thread 2 
Value = 0   
        Value = 2 
int x1 = Value  
WriteLine(x1)  
        int x2 = Value 
        WriteLine(x2) 

你会得到一个错误结果因为两个线程,你会读出的数值2.其实这不是因为锁定的唯一操作是集,线程2的写入操作(属性值的设置)之前将不执行线程1的读取操作(属性值的获取)。

最后再看看this post如果你这样写:

将会看到这样的代码可能会失败(完全是出于同样的原因)
++_concurrency.Value; 
+0

好吧,我已经知道,我知道锁的位置是错误的。但是,为什么 - 无论我运行这个应用程序多少次 - 只有第一个设置/获取失败,其他都可以? – 2013-03-04 12:51:22

+0

随机对象的“共享”种子可能有所贡献(毕竟锁定释放和属性获得之间的时间间隔非常短),但重点是您不能依赖/研究/检查指令执行顺序不同的线程。至少没有这么高的水平。 – 2013-03-04 13:00:26

+0

也许(因为我之前说过的原因,这只是一个无用的猜测),因为第一次运行第二个线程时已经过去了(因为在创建第二个任务时第一个任务还在运行)。 – 2013-03-04 13:03:26