【问题标题】:C#: Sharing a class member between threadsC#:在线程之间共享类成员
【发布时间】:2017-06-22 22:15:23
【问题描述】:

我一直在开发多线程算法,我对在 C# 中的线程之间共享类成员有些疑问。

假设我们有两个类算法和处理器。处理器有一个 main 方法 DoWork 和附加的 AvaliableResources 方法,该方法偶尔调用以更改可用于处理的资源的数量。 Algorithm 对象中的方法 RunUpdateResources两个不同的线程调用,可能在不同的核心上工作。

是否有可能 _processor 变量将存储在 CPU 的缓存中并且永远不会上传到内存中,并且永远不会调用 AvaliableResources 因为 _processor 第二次为空线程?

class Processor
{

    public void DoWork() { ...  }
    public void AvaliableResources(int x) { ... }
}

class Algorithm
{
    private Processor _processor;

    public void Run()
    {
        _processor = new Processor();
        _processor.DoWork();
        _processor = null;
    }

    public void UpdateResources(int x)
    {
        _processor?.AvaliableResources(x);
    }
}

如果存在同步问题,以下代码是否可以解决?

备选方案 1

class Processor
{
    public void DoWork() { ... }
    public void UpdateResources(int x) { ... }
}

class Algorithm
{
    private volatile Processor _processor; // added volatile keyword

    public void Run()
    {
        _processor = new Processor();
        _processor.DoWork();
        _processor = null;
    }

    public void UpdateResources(int x)
    {
        _processor?.UpdateResources(x);
    }
}

备选方案 2

class Processor
{
    public void DoWork() { ... }
    public void UpdateResources(int x) { ... }
}

class Algorithm
{
    private Processor _processor;

    public void Run()
    {
        _processor = new Processor();
        Thread.MemoryBarrier(); // Added memory barier
        _processor.DoWork();
        _processor = null;
    }

    public void UpdateResources(int x)
    {
        Thread.MemoryBarrier(); // Added memory barier
        _processor?.UpdateResources(x);
    }
}

编辑: 正如您在 cmets 中所建议的,请查看更好的解释代码:

    class Processor
    {
        private int resources = Environment.ProcessorCount;

        public void DoWork()
        {
            /*do some long running job using avaliable resources*/
        }

        public void UpdateResources(int x)
        {
            resources = x;
        }
    }

    class Algorithm
    {
        private volatile Processor _processor;

        public void Run()
        {
            _processor = new Processor();
            _processor.DoWork();
            _processor = null;
        }

        public void UpdateResources(int x)
        {
            _processor?.UpdateResources(x);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var algorithm = new Algorithm();
            var result = Task.Run(() => algorithm.Run());
            // The resources were required for other staff and are reduced
            algorithm.UpdateResources(1);
            // The resources are reassigned for the long running algorithm 
            algorithm.UpdateResources(10);
            // wait until the algorithm finishes
            result.Wait();
            // this update should have no effect as the Run method has finished and _processor is null
            algorithm.UpdateResources(10);
        }
    }

【问题讨论】:

  • 运行代码时发生了什么?
  • 还要考虑stackoverflow.com/questions/3556351/… 是否无法解决您的问题
  • "for the second thread" - 两个线程都不是第一个或第二个线程 - 它们只是不同的线程; “第二个”线程可以在 Run 甚至被调用之前完成它需要做的所有事情......在这种情况下:_processor 的值是什么?
  • @Fabulous 看起来很相似,但我担心它是否也适用于引用类型。他们正在讨论布尔标志,它是值类型,在另一种类型上,引用是一个指针,它只是一个值。因此我的直觉说行为应该是相同的,我应该使用 MemoryBarrier 来确保可移植性。
  • @MarcGravell 我知道,这只是我必须调用线程#1 和线程#2 的术语。而且它与执行顺序无关:)。

标签: c# multithreading thread-safety clr


【解决方案1】:

如果您想访问,则 2 选择是错误的 _processor obj 这种方式。

无论如何,您应该在处理器中使用 null,因为 DoWork 运行和完成速度更快。 尝试寻找相同的第二个第一个睡眠线程并检查是否已执行。

您的代码没有提供足够的信息来建议正确的方法。

使用 volatile 关键字是正确的,但如果 R​​un 快速可用则不调用 never

编辑

好的,很好的例子。但问题是在执行 UpdateResources 之前运行完成的主要工作人员。

如果您希望 UpdateResources 在等待前执行两次,则插入在等待后执行 _processor= null 的新方法。 在某种程度上,每次使用此代码时,每次在等待之前调用 UpdateResources,您的风险是 _processor 为空。

在等待之前取决于你想要什么

【讨论】:

  • volatile 是一个非常微妙和微妙的野兽;大多数人们认为volatile 的“效果”实际上只是次要的副作用和实现细节。要求任何人定义volatile 实际上是什么意思,以及这对特定情况有何帮助……好吧,除了少数 CPU 专家,大多数人都无法给出一个好的答案。我把自己包括在“无法给出好的答案”这一组中
  • 我不明白你想用这个线程做什么。如果您想要针对您的问题或其他问题的提示。 Volatile 用于始终在变量中具有最新的值。此关键字与编译器通信以优化多线程访问的值。无论如何,如果运行完成,您的代码不会执行更新资源。只有在doWork执行期间调用方法更新资源才会被调用
  • @Pasalino 我添加了更好的示例,希望对您有所帮助。
  • @Pasalino 我知道我随时冒 _processor 为空的风险,但它的正确行为 - UpdateResources 只有在 DoWork 正在进行时才有意义。如果在 UpdateResources 调用之前 DoWork 已经完成,则 update 没有意义,并且不会调用 UpdateResources。但是,我担心的是 CPU 是否可以优化 _processor = new Processor() 行并将 _processor 值保留在 CPU 寄存器(或缓存)中,从不将其存储到内存中,因此线程 #2 将始终为空(尽管 _processor 还活着与否)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-06
  • 1970-01-01
  • 2018-07-30
  • 2010-11-23
  • 1970-01-01
相关资源
最近更新 更多