2010-08-29 123 views
29
class Unit { 
    private readonly string name; 
    private readonly double scale; 

    public Unit(string name, double scale) { 
     this.name = name; 
     this.scale = scale, 
    } 

    public string Name { get { return name; } } 
    public string Scale { get { return scale; } } 

    private static Unit gram = new Unit("Gram", 1.0); 

    public Unit Gram { get { return gram; } } 
} 

多线程可以访问Unit.Gram。为什么多线程同时读取Unit.Gram.Title为什么不可变对象是线程安全的?

我担心的是他们指的是相同的内存位置。一个线程开始读取该内存,那么是不是“锁定”呢? .NET是否处理下面这个关键部分的同步?或者我错在认为同步阅读需要同步?

+0

“我担心的是他们指的是相同的内存位置。”他们?也许。这不是你关心的问题,这是虚拟机的问题。同步阅读不需要同步,为什么呢? – 2010-08-29 14:43:37

+2

*“为什么会这样”* >>这不是问题吗?如果我读到女友读的同一本书,同时我们总是会为此而战。我知道它在计算机上并不是问题(两个线程同时读取不会发生,CPU架构会照顾它),但我觉得它是一个非常合法的问题:) – Abel 2010-08-29 14:58:54

+0

读一本书涉及一个可变状态(当前页面,闪电等)。你不是在为这本书而战,而是在处理它。 – sisve 2010-08-29 17:27:24

回答

11

我认为你的问题原来并不被线程安全或不可变性,但关于内存访问的(非常)低级细节。

这是一个很大的问题,但简短的回答是:是的,两个线程(更重要的是,两个CPU)可以同时读取(和/或写入)同一块内存。

只要该内存区域的内容是不变的,所有的问题都解决了。当它可以改变时,存在一系列问题,volatile关键字和Interlocked类是我们用来解决这些问题的一些工具。

+1

如您所说,在低层次上,我相信内存的同时访问不会发生。处理器不能同时访问相同的内存,除非该内存恰好被缓存。否则,必须使用内存总线,此时只能由一个处理器使用。这会阻碍处理器并限制多处理器可能提供的性能增益(内存访问速度比CPU慢多倍)。一种补救措施是[NUMA概念或体系结构](http://en.wikipedia.org/wiki/Non-Uniform_Memory_Access),其中单独的总线用于访问(仍然是单独的)内存位置: – Abel 2010-08-29 16:33:10

+0

@Abel,你是对的但我称之为“短篇小说”:就我们(程序员)而言,它是同步的。即使是汇编语言程序员。 – 2010-08-29 17:37:54

+0

我相信你是对的。但是,透明地处理存储器读取同步的分支,正是因为线程安全性的不变性。实际的答案似乎是因为**它在**下处理,并且质疑它是否被处理并不是那么愚蠢。 :) – randomguy 2010-08-29 20:54:34

4

如果对象是不可变的,它的状态将永远不会改变。因此,陈旧数据的担忧就会消失。线程读取从不锁定,所以这是一个非问题(死锁)

57

什么使对象不是线程安全的?如果对象的值/状态在线程正在读取时可能发生更改,则该对象不是线程安全的。这通常发生在第二个线程在第一个线程正在读取时更改此对象的值。

根据定义,不可变对象不能更改值/状态。由于每次读取不可变对象时都具有相同的值/状态,因此您可以让任意数量的线程读取该对象而无需担心。

+15

你可以补充说,当一个不可变的对象被初始化时,它的状态是* does *改变,但是CLR保证这个过程是线程安全的(即,当init进程不是另一个线程试图读取时在ctor或cctor完成)。 – Abel 2010-08-29 15:01:01

+0

@Abel:我觉得你的评论是正确的,但是你能否提供一个支持你的说法的语言标准?特别是,为什么在构造函数中的写入操作不可能移过对象发布? – Vlad 2017-11-28 16:43:19

11

同时读取不需要需要同步。由于只有作者(或读者至少有一个作者)需要同步,因此不可变对象不需要同步,因此是线程安全的。

3

并行读取在绝大多数情况下都不需要同步(例外情况是内存映射IO,其中读取某个地址会导致副作用)。

如果并发读取确实需要同步,那么几乎不可能写出有用的多线程代码。为了执行一段代码,处理器必须读取指令流,如果函数本身必须防止自己同时执行,那么如何编写锁定函数?

+2

公平地说,在某种程度上,硬件必须处理这些问题。但它是用硬件完成的,甚至对于操作系统来说都是不可见的,对.NET应用程序编写者来说什么都不说。 – siride 2010-08-29 14:42:36

+0

@siride,我真的觉得我应该接受你的评论作为答案,因为它实际上回答了这个问题。 **同步处理在下面。**之后,很容易理解不变性恕我直言。 – randomguy 2010-08-29 20:57:36

+1

@randomguy,嗯,硬件确实需要在某些层面处理这些问题,但它也在一些层次上首先介绍了这些需求。如果你想说它是“在底下处理的”,你必须说的是“在某些计算机设计中,并发读取可能由于设计选择而导致问题,软件受此影响的可能性通过各种机制被消除,其他设计是不受并发读取的影响,还有一些并发读取是不可能的。“ – 2010-08-29 21:31:41

1

Memory Management Units是处理内存读取的处理器的一部分。如果你有一个以上的蓝色月亮,其中两个可能会尝试在相同的十几纳秒内读取相同的内存位置,但没有问题导致看到他们得到相同的答案。

1

除了驱动程序的映射内存等异常外,两个线程同时读取同一内存地址也没有问题。当一个线程执行某些写入数据时可能会出现问题。在这种情况下,可以防止其他线程读取该对象/数据。

但问题是不是由于著作同时性(在最低电子水平它们发生一个在另一个之后无论如何),问题是宁可设置数据的对象/可能会失去它们的一致性。通常利用一个section critic来隔离一些可能不被其他线程同时读/写的代码。

有过网的例子很多,但是考虑到后面,用price是一类的私有成员,说产品,这也2种方法

public void setPrice(int value) { 
    price = value; 
    // -- point CRITIC -- 
    price += TAX; 
} 

public int getPrice() { 
    return price; 
} 

setPrice(v)设置的价格一个产品到V,并用增值税进行调整(程序应该有value += TAX; price = value,但这不是重点:-)

如果线程A写入价格100,并且税收(固定)1,产品价格将最终被设置为101.但是如果线程B通过getPrice()读取价格而线程A在point CRITIC处会发生什么?返回给B的价格将错过税收,并且是错误的。

setPrice()应该在关键部分(lock)内,以防止价格的设定过程中对对象的任何访问

lock(this) 
    { 
     price = value; 
     price += TAX; 
    } 
2

读取相同的内存位置只能在一个CPU周期内由特定线程完成。在这种情况下读取顺序无关紧要,因为基础值不会改变。因此读取不可能不一致,所以在这种情况下不需要任何级别的同步。

+0

我认为这也回答了这个问题真的很好。你是否想扩展这个答案? – randomguy 2010-08-29 21:02:03

+0

实际上,2个CPU可以读取在相同的时钟周期中有相同的地址(并且看到不同的值),这是因为它们每个都会从它们自己的缓存中读取数据 – 2010-08-29 21:09:56

+0

这是正确的,但为了简化问题,我不得不假设单个CPU情况。但是,在多CPU情况下唯一改变的部分是内存缓存的可能性,但值仍然是相同的。 尽管变量gram被声明为静态,但它没有“setter”,它是初始化为1.0的值,因此它是不可变的,不需要任何程序同步。 – venky 2010-08-29 21:52:03

相关问题