C#ThreadStatic + volatile成员未按预期工作

时间:2012-06-12 09:10:01

标签: c# multithreading volatile threadstatic

我正在阅读tips and tricks帖子,我想我会尝试一些我之前从未做过的C#。因此,下面的代码没有任何实际用途,但只是一个“测试函数”来看看会发生什么。

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

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

如您所见,我正在测试 ThreadStaticAttribute volatile 关键字。

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

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

输出的第二个'一半'是预期的(我想这意味着ThreadStatic字段的工作方式与我想的一样),但似乎有一些初始输出已经从第一个'半'跳过'

此外,第一个“half”中的线程出现故障,但我知道一旦调用Start(),线程就不会立即运行;但相反,内部OS控件将在它认为合适时启动线程。 编辑:不,他们不是,实际上,我只是认为他们是因为我的大脑错过了连续数字


所以,我的问题是:导致我在输出的前半部分丢失几行会出现什么问题?例如,'线程3将threadInt设置为84 '?

2 个答案:

答案 0 :(得分:8)

线程正在同时执行。概念上发生的是:

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写回
  7. 这导致你的线路丢失。挥发无助于此;连接字符串的整个过程不是原子的。你需要围绕这些操作使用锁定:

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

答案 1 :(得分:2)

MSDN描述了关键字volatile的作用here

  

volatile关键字表示字段可能被修改   多个并发执行的线程。声明的字段   volatile不受假定访问的编译器优化的影响   通过一个线程。这可确保获得最新的价值   在任何时候都在现场。

这意味着,在您的示例中,会发生什么 about this(这可能会不时变化;取决于调度程序):

  • 线程0正在读取staticVolatileTestString
  • 之外的字符串
  • 线程0正在附加&#39;线程0将threadInt设置为88&#39;
  • 线程0将字符串写回staticVolatileTestString

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

  • 主题1-4正在从staticVolatileTestString
  • 中读取字符串
  • 主题1正在附加&#39;主题1将threadInt设置为97&#39;
  • 主题2正在附加&#39;主题2将threadInt设置为11&#39;
  • 主题2正在将字符串写回staticVolatileTestString
  • ...主题1,2,3正在阅读和追加,以及写作
  • 主题4正在将字符串写回staticVolatileTestString
  • 等......

看看这里发生了什么?线程4读取字符串&#39;线程0设置threadInt到88&#39;,附加其线程4 ...&#39;并写回来,覆盖线程1,2和3已经写入字符串的所有内容。