C# 线程真的可以缓存一个值并忽略其他线程上对该值的更改吗? [英] Can a C# thread really cache a value and ignore changes to that value on other threads?

查看:18
本文介绍了C# 线程真的可以缓存一个值并忽略其他线程上对该值的更改吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个问题不是关于竞争条件、原子性或为什么应该在代码中使用锁.我已经知道这些了.<​​/p>

更新:我的问题不是是否存在易失性内存的怪异性"(我知道确实存在),我的问题是.NET 运行时不会将其抽象化,因此您永远不会看到它".

参见 http://www.yoda.arachsys.com/csharp/线程/波动率.shtml以及关于 字符串属性本身线程安全吗?

的第一个答案

(它们实际上是同一篇文章,因为一个引用了另一个.)一个线程设置一个布尔值,另一个线程循环永远读取该布尔值——这些文章声称读取线程可能会缓存旧值而永远不会读取新值值,因此您需要一个锁(或使用 volatile 关键字).他们声称以下代码可能会永远循环.现在我同意锁定变量是一种很好的做法,但我不敢相信 .NET 运行时真的会忽略文章声称的内存值变化.我理解他们关于易失性内存与非易失性内存的讨论,我同意他们在非托管代码中有一个有效的观点,但我不敢相信 .NET 运行时不会正确地抽象出以便以下代码可以满足您的期望.这篇文章甚至承认代码几乎肯定"会起作用(虽然不能保证),所以我在声明中打电话给 BS.任何人都可以验证以下代码并不总是有效吗?有没有人能够得到一个失败的案例(也许你不能总是重现它)?

class BackgroundTaskDemo{私人布尔停止=假;静态无效主(){BackgroundTaskDemo demo = new BackgroundTaskDemo();新线程(演示.DoWork).开始();线程.睡眠(5000);演示.停止=真;}静态无效 DoWork(){而(!停止){//在这里做点什么}}}

解决方案

重点是:它可能会起作用,但不能保证按照规范工作.人们通常追求的是出于正确原因工作的代码,而不是靠编译器、运行时和 JIT 的侥幸组合来工作,后者可能在框架版本、物理 CPU、平台,以及诸如 x86 与 x64 之类的东西.

理解内存模型是一个非常非常复杂的领域,我不自称是专家;但是该领域真正专家的人向我保证,您所看到的行为并不能得到保证.

您可以根据需要发布尽可能多的工作示例,但不幸的是,除了它通常有效"之外,并不能证明太多.这当然不能证明它保证工作.只需要一个反例就可以反驳,但发现它是问题......

不,我手头没有.

<小时>

用可重复的反例更新:

使用 System.Threading;使用系统;静态类 BackgroundTaskDemo{//使这个 volatile 修复它私人静态布尔停止=假;静态无效主(){新线程(DoWork).开始();线程.睡眠(5000);停止 = 真;Console.WriteLine("主出口");Console.ReadLine();}静态无效 DoWork(){int i = 0;而(!停止){我++;}Console.WriteLine("DoWork 退出" + i);}}

输出:

主出口

但仍在运行,CPU 满载;请注意,此时 stopping 已设置为 true.ReadLine 是为了使进程不会终止.优化似乎取决于循环内代码的大小(因此是 i++).它显然只适用于发布"模式.添加 volatile 一切正常.

This question is NOT about race-conditions, atomicity, or why you should use locks in your code. I already know about those.

UPDATE: My question isn't "does weirdness with volatile memory exist" (i know it does), my question is "doesn't the .NET runtime abstract that away so you'll never see it".

See http://www.yoda.arachsys.com/csharp/threads/volatility.shtml and the first answer on Is a string property itself threadsafe?

(They're really the same article since one references the other.) One thread sets a bool and the other thread loops forever reading that bool -- those articles claim the reading thread might cache the old value and never read the new value, so therefore you need a lock (or use the volatile keyword). They claim the following code will potentially loop forever. Now I agree it's good practice to lock your variables, but I can't believe the .NET runtime would really ignore a memory value changing as the article claims. I understand their talk about volatile memory vs non-volatile memory, and I agree they have a valid point in non-managed code, but I can't believe the .NET runtime won't correctly abstract that away so that the following code does what you expect. The article even admits the code will "almost certainly" work (though not guaranteed), so I'm calling BS on the claim. Can anyone verify that it's true the following code won't always work? Is anyone able to get even one case (maybe you can't always reproduce it) where this fails?

class BackgroundTaskDemo
{
    private bool stopping = false;

    static void Main()
    {
        BackgroundTaskDemo demo = new BackgroundTaskDemo();
        new Thread(demo.DoWork).Start();
        Thread.Sleep(5000);
        demo.stopping = true;
    }

    static void DoWork()
    {
         while (!stopping)
         {
               // Do something here
         }
    }
}

解决方案

The point is: it might work, but it isn't guaranteed to work by the spec. What people are usually after is code that works for the right reasons, rather than working by a fluke combination of the compiler, the runtime and the JIT, which might change between framework versions, the physical CPU, the platform, and things like x86 vs x64.

Understanding the memory model is a very very complex area, and I don't claim to be an expert; but people who are real experts in this area assure me that the behaviour you are seeing is not guaranteed.

You can post as many working examples as you like, but unfortunately that doesn't prove much other than "it usually works". It certainly doesn't prove that it is guaranteed to work. It would only take a single counter-example to disprove, but finding it is the problem...

No, I don't have one to hand.


Update with repeatable counter-example:

using System.Threading;
using System;
static class BackgroundTaskDemo
{
    // make this volatile to fix it
    private static bool stopping = false;

    static void Main()
    {
        new Thread(DoWork).Start();
        Thread.Sleep(5000);
        stopping = true;


        Console.WriteLine("Main exit");
        Console.ReadLine();
    }

    static void DoWork()
    {
        int i = 0;
        while (!stopping)
        {
            i++;
        }

        Console.WriteLine("DoWork exit " + i);
    }
}

Output:

Main exit

but still running, at full CPU; note that stopping has been set to true by this point. The ReadLine is so that the process doesn't terminate. The optimization seems to be dependent on the size of the code inside the loop (hence i++). It only works in "release" mode obviously. Add volatile and it all works fine.

这篇关于C# 线程真的可以缓存一个值并忽略其他线程上对该值的更改吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆