这是一个类字段,
class someClass {
Int someClassField = nil
...
(请(!)忽略可见性问题,这个问题与整体设计有关,而不是语言实现)如果我在线查看教程,我被告知这个字段可以安全地使用多个线程。当教程说安全时,它们并不意味着一个线程不会干扰另一个线程可见的值。这种干扰可能是意图 - 该字段可能是一个计数器。本教程的含义是,当一个线程更改此字段时,该字段不会处于不安全状态。拿这个字段,
class someClass {
List<List> someClassField = new List<Int>()
...
据我所知,如果该字段是一个简单的列表,则一个线程可能使其处于不一致状态(即部分断开)。如果另一个线程使用该列表,它将失败 - 用C语言这将是一场灾难。即使阅读也可能失败。
那么,可以要求在该领域使用的类复制出它的状态(复制可以扩展到完全防御不变性,但我保持讨论简单)。如果类复制了它的状态,则在修改后返回的新副本中,远离字段上的副本进行修改。可以将此新修改的副本重新分配给该字段。但是这个赋值线程是否安全 - 在某种意义上,该字段的值不能处于不一致状态 - 因为新对象的引用分配给该字段是原子的?
我忽略了语言引擎可能重新排序,缓存等所有问题。请参阅下面的许多帖子(特别是Java,似乎),
我想以较小的规模来处理这个问题......
答案 0 :(得分:1)
在大多数语言中,对象分配是原子的。
在这种特定情况下,您需要小心,因为在执行x=new X()
时,并不能保证所有语言中X在分配之前已完全初始化。我不确定C#在哪里。
您还必须考虑可见性和原子性。例如,在Java中,您需要将变量设置为volatile,否则在一个线程中所做的更改可能根本不会在另一个线程中可见。
答案 1 :(得分:1)
C ++将数据争用定义为可能同时访问同一内存位置的两个或多个线程,其中至少有一个是修改。具有数据争用的程序的行为是未定义的。所以不,如果多个线程中至少有一个可以修改它,那么多个线程访问该字段是不安全的。
答案 2 :(得分:1)
在Java中,除了64位原始类型long
和double
之外,对引用和原语的赋值是原子的。通过使用long
修饰符声明对Java double
和volatile
的赋值可以使其成为原子分配。请参阅:Are 64 bit assignments in Java atomic on a 32 bit machine?
之所以如此,是因为Java VM规范需要它才能使VM符合Java标准。
Scala运行在标准Java VM之上,因此除了开始使用JNI之外,它还将提供与分配相同的Java保证。
C / C ++的一个问题(以及它的优势之一)是两种语言都允许将数据结构非常精细地映射到内存地址。在这个级别,对内存的写入是否是原子的非常依赖于硬件平台。例如,CPU通常无法原子读取,更不用说写入未正确对齐的变量。例如当16位变量未与偶数地址对齐时,或者32位变量未与4的倍数对齐时,依此类推。当变量超出一个缓存行进入下一个缓存行或超过一个页面进入下一个页面时,情况会变得更糟。因此,C不保证分配是原子的。
答案 3 :(得分:1)
在Java中编写引用是原子的(只有当字段是易变的时才写入long或double,但是这根本不会对你有所帮助。
演示示例:
class Foo {
int x;
public Foo() { x = 5};
}
现在假设我们做了foo = new Foo()
这样的赋值(没有foo的final或volatile修饰符!)。从低层次来看,这意味着我们必须做到以下几点:
但只要构造函数没有读取我们将其分配给的字段,编译器就可以执行以下操作:
主题安全的吗?当然不是(如果你没有设置内存障碍,你永远不能保证真正看到更新)。当涉及最终字段时,Java提供了更多保证,因此创建新的不可变对象将是线程安全的(您永远不会看到最终字段的未初始化值)。易失性字段(我们在这里谈论的是赋值,而不是对象中的字段)在java和c#中都避免了这个问题。不确定C#和readonly。