3

我已经阅读了一些关于volatile没有这个关键字的关键字和行为的帖子。

我特别测试了Illustrating usage of the volatile keyword in C#的答案中的代码。运行时,我观察到发布模式下的异常行为,没有附加调试器。到那时为止,没有问题。

因此,据我了解,以下代码永远不应该退出。

public class Program
{
    private bool stopThread;

    public void Test()
    {
        while (!stopThread) { }  // Read stopThread which is not marked as volatile
        Console.WriteLine("Stopped.");
    }


    private static void Main()
    {
        Program program = new Program();

        Thread thread = new Thread(program.Test);
        thread.Start();

        Console.WriteLine("Press a key to stop the thread.");
        Console.ReadKey();

        Console.WriteLine("Waiting for thread.");
        program.stopThread = true;

        thread.Join();  // Waits for the thread to stop.
    }
}

为什么会退出?即使在发布模式下,没有调试器?

更新

改编自Illustrating usage of the volatile 关键字 in C#中的代码。

private bool exit;

public void Test()
{
    Thread.Sleep(500);
    exit = true;
    Console.WriteLine("Exit requested.");
}

private static void Main()
{
    Program program = new Program();

    // Starts the thread
    Thread thread = new Thread(program.Test);
    thread.Start();

    Console.WriteLine("Waiting for thread.");
    while (!program.exit) { }
}

该程序在发布模式后不会退出,没有附加调试器。

4

2 回答 2

10

因此,据我了解,以下内容永远不应该退出。

不,它可以停止。只是不能保证

例如,它不会在我当前正在运行的机器上停止 - 但同样我可以在另一台机器上尝试完全相同的可执行文件,它可能表现得很好。这将取决于运行它的 CLR 使用的确切内存模型语义。这将受到底层架构的影响,甚至可能受到正在使用的确切 CPU 的影响。

重要的是要注意,决定如何处理字段的不是C# 编译器volatile- C# 编译器只是使用System.Runtime.CompilerServices.IsVolatile. 然后,JIT可以计算出遵守相关合同的含义。

于 2013-07-28T12:01:11.130 回答
1

在评论中您说您的目标是 32 位 x86 架构。这个很重要。另外,我的回答是假设已经意识到仅仅因为内存模型允许某些事情发生并不意味着它总是会发生。

简短的回答:

这是因为while循环是空的。当然,许多其他细微的变化也会影响行为。例如,如果你在循环之前Console.WriteLine或之前调用,那么行为将会改变。Thread.MemoryBarrier

长答案:

32 位和 64 位运行时的行为方式有所不同。无论出于何种原因,32 位运行时都会在循环之前没有显式/隐式内存生成器或while循环本身为空的情况下进行提升优化。

考虑我在此处关于同一主题的另一个问题的示例。这里又是下面。

class Program
{
    static bool stop = false;

    public static void Main(string[] args)
    {
        var t = new Thread(() =>
        {
            Console.WriteLine("thread begin");
            bool toggle = false;
            while (!stop)
            {
                toggle = !toggle;
            }
            Console.WriteLine("thread end");
        });
        t.Start();
        Thread.Sleep(1000);
        stop = true;
        Console.WriteLine("stop = true");
        Console.WriteLine("waiting...");

        // The Join call should return almost immediately.
        // With volatile it DOES.
        // Without volatile it does NOT.
        t.Join(); 
    }
}

这个例子确实重现了 32 位 x86 硬件上的“不退出”行为。请注意我是如何故意让while循环忙于做某事的。无论出于何种原因,一个空循环都不会始终如一地重现该行为。现在,让我们使用从上面学到的知识来更改您的第一个示例,看看会发生什么。

public class Program
{
    private bool stopThread;

    public void Test()
    {
        bool toggle = true;
        while (!stopThread) // Read stopThread which is not marked as volatile
        { 
          toggle = !toggle;
        }  
        Console.WriteLine("Stopped.");
    }


    private static void Main()
    {
        Program program = new Program();

        Thread thread = new Thread(program.Test);
        thread.Start();

        Console.WriteLine("Press a key to stop the thread.");
        Console.ReadKey();

        Console.WriteLine("Waiting for thread.");
        program.stopThread = true;

        thread.Join();  // Waits for the thread to stop.
    }
}

使用您的第一个示例的略微修改版本来让while循环执行某些操作,您会看到它现在开始表现出“不退出”行为。我刚刚使用 .NET 4.5 在 Windows 7 64 位上针对 32 位 x86 进行了测试。我相信您也应该注意到环境的变化。试试上面的修改。

于 2013-07-28T19:07:01.980 回答