2012-06-12 71 views
6

我正在阅读tips and tricks文章,我想我会尝试一些以前从未做过的C#内容。因此,下面的代码没有实际用途,但仅仅是一个'测试函数'来看看会发生什么。C#ThreadStatic + volatile成员不按预期方式工作

无论如何,我有两个静态私有字段:

private static volatile string staticVolatileTestString = ""; 
[ThreadStatic] 
private static int threadInt = 0; 

正如你所看到的,我测试ThreadStaticAttribute挥发性关键字。

无论如何,我有一个测试方法,看起来像这样:

private static string TestThreadStatic() { 
    // Firstly I'm creating 10 threads (DEFAULT_TEST_SIZE is 10) and starting them all with an anonymous method 
    List<Thread> startedThreads = new List<Thread>(); 
    for (int i = 0; i < DEFAULT_TEST_SIZE; ++i) { 
     Thread t = new Thread(delegate(object o) { 
      // The anon method sets a newValue for threadInt and prints the new value to the volatile test string, then waits between 1 and 10 seconds, then prints the value for threadInt to the volatile test string again to confirm that no other thread has changed it 
      int newVal = randomNumberGenerator.Next(10, 100); 
      staticVolatileTestString += Environment.NewLine + "\tthread " + ((int) o) + " setting threadInt to " + newVal; 
      threadInt = newVal; 
      Thread.Sleep(randomNumberGenerator.Next(1000, 10000)); 
      staticVolatileTestString += Environment.NewLine + "\tthread " + ((int) o) + " finished: " + threadInt; 
     }); 
     t.Start(i); 
     startedThreads.Add(t); 
    } 

    foreach (Thread th in startedThreads) th.Join(); 

    return staticVolatileTestString; 
} 

我希望看到从该函数返回的是一个像这样的输出:

thread 0 setting threadInt to 88 
thread 1 setting threadInt to 97 
thread 2 setting threadInt to 11 
thread 3 setting threadInt to 84 
thread 4 setting threadInt to 67 
thread 5 setting threadInt to 46 
thread 6 setting threadInt to 94 
thread 7 setting threadInt to 60 
thread 8 setting threadInt to 11 
thread 9 setting threadInt to 81 
thread 5 finished: 46 
thread 2 finished: 11 
thread 4 finished: 67 
thread 3 finished: 84 
thread 9 finished: 81 
thread 6 finished: 94 
thread 7 finished: 60 
thread 1 finished: 97 
thread 8 finished: 11 
thread 0 finished: 88 

然而,是什么我得到的是这样的:

thread 0 setting threadInt to 88 
thread 4 setting threadInt to 67 
thread 6 setting threadInt to 94 
thread 7 setting threadInt to 60 
thread 8 setting threadInt to 11 
thread 9 setting threadInt to 81 
thread 5 finished: 46 
thread 2 finished: 11 
thread 4 finished: 67 
thread 3 finished: 84 
thread 9 finished: 81 
thread 6 finished: 94 
thread 7 finished: 60 
thread 1 finished: 97 
thread 8 finished: 11 
thread 0 finished: 88 

第二个“一半”的输出是预期的(我想这意味着th在ThreadStatic领域工作就像我想的那样),但是似乎有些初始输出已经从第一个'half'中“跳过”了。

此外,第一个'half'中的线程出现故障,但我知道一旦您调用Start(),线程就不会立即运行;而是内部操作系统控制器会根据它认为合适的方式启动线程。 编辑:没有他们没有,其实,我只是以为他们是因为我的大脑错过了连续数


所以,我的问题是:什么错误导致我失去的几行第一个'一半'的输出?例如,线'线程3设置threadInt到84'在哪里?

+0

我运行你的代码几次,总是得到预期的输出... –

+0

我想下注的地方,对字符串+ =的调用是在上一个线程设置它之前获取值,新线程将用新值覆盖它(因此线程1已将其输出跳过)。 – Charleh

+0

@DannyChen我在一个四核上,这不是在应用程序中运行的唯一代码;不知道这两者是否有意义。如果你愿意,我可以上传整个代码(只有两个.cs文件)。 – Xenoprimate

回答

7

线程正在同时执行。什么概念发生的情况是这样的:

staticVolatileTestString += Environment.NewLine + "\tthread " + ((int) o) + " setting threadInt to " + newVal; 
  1. 线程1读取staticVolatileTestString
  2. 线程2读取staticVolatileTestString
  3. 线程3读取staticVolatileTestString
  4. 线程1追加的东西,并写入staticVolatileTestString回
  5. 线程2追加东西并写入staticVolatileTestString返回
  6. 线程3追加东西并写入staticVolatileTestString返回

这会导致您的线路丢失。挥发性对此没有帮助;连接字符串的整个过程不是原子的。您需要使用锁围绕这些操作:

private static object sync = new object(); 

lock (sync) { 
    staticVolatileTestString += Environment.NewLine + "\tthread " + ((int) o) + " setting threadInt to " + newVal; 
} 
+0

是的,我只说了很多更好地解释:D哈哈+1 – Charleh

2

的MSDN介绍了这些关键字volatile确实here

volatile关键字指示一个字段可能由 多个并发执行多个线程进行修改。声明为 易失性的字段不受编译器优化的限制,假设单个线程访问 。这确保了最新的数值始终在现场出现在 。

这意味着,在你的榜样,会发生什么情况是这个(这可以改变不时;取决于调度):

  • 线程0被读取串出的staticVolatileTestString
  • 线程0被追加 '线程0设置到threadInt 88'
  • 线程0被写入字符串回staticVolatileTestString

这就像预期到目前为止,但随后:

  • 螺纹1-4正在阅读的串出staticVolatileTestString
  • 线程1的被附加 '线程1设置threadInt至97'
  • 螺纹2正在添加 '线程2设置threadInt 11'
  • 线2写入字符串回staticVolatileTestString
  • ...线程1,2,3读取和追加,写
  • 线程4是写入字符串回staticVolatileTestString
  • 等等...

看到这里发生了什么?线程4读取字符串'线程0设置threadInt为88',附加它的'线程4 ...'并写回,覆盖已写入字符串的所有线程1,2和3。

+0

那么你是说线程正在同时读取值,然后作为一个单独的操作写在彼此?如果是这样的话,我需要在字段上进行一些锁定(这是我认为的不稳定性,但我想这实际上只是确保没有缓存,对吧?)编辑:Lucero的答案证实了这个 – Xenoprimate

+0

@Motig:读取字符串时,锁定在那里,或写回,但不在中间。请记住:字符串是不可变的,因此会创建一个新的字符串实例,然后将其引用写入变量中。 –

+2

@Motig:volatile关键字对于可变原始数据类型(bool,int,double等)很有效。但是,对于字符串,您需要设置专用锁,读取字符串,修改并写回,然后释放锁。 –