RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 692793
Accepted
Pavel
Pavel
Asked:2020-07-16 15:30:48 +0000 UTC2020-07-16 15:30:48 +0000 UTC 2020-07-16 15:30:48 +0000 UTC

如果存在 True/False 共享,为什么会不稳定?

  • 772

有 True 和 False 共享,允许处理器交换高速缓存行。这怎么会有可见性问题?

如果共享允许核心查看彼此的缓存,那么它解决的问题是什么volatile?

或者你可以这样改写:为了防止在受控竞争状态下的数据泄漏,共享机制缺少什么?

共享可能有效,但它只适用于缓存中已经有这个变量的那些处理器,并且招募线程可以从内存中读取不再相关的数据,因为那些线程已经使用这个变量很长时间了时间变量 - 在最后一次卸载到内存后你是否设法改变了它的值?

也就是说,在第一次从内存中读取变量到第一次更改之间的间隔内,共享不起作用?(作为假设)。

更新: “如果缓存一致性协议要求处理器缓存将内存位置保持在一致状态,那么为什么我们需要 volatile,它的作用相同?”

java
  • 3 3 个回答
  • 10 Views

3 个回答

  • Voted
  1. Best Answer
    damintsew
    2020-07-18T04:20:45Z2020-07-18T04:20:45Z

    虚假分享

    False Sharing 是一个术语,用于描述当不同线程修改恰好在同一缓存行上的独立变量时出现不良性能下降的机制。

    阅读Habr的精彩文章,其中描述了这些机制。简而言之,这是引用:

    在这种情况下,如果其中一个线程修改了其结构的一个字段,则根据缓存一致性协议,整个缓存行对于其余处理器内核将被声明为无效。另一个线程将无法再使用它的结构,尽管它已经在其内核的 L1 缓存中。在 P4 等较老的处理器中,这种情况需要与主存进行长时间的同步,即修改后的数据会被发送到主存,然后读入另一个核心的 L1 缓存。

    易挥发的

    volatile 修饰符是引入该语言的 java 关键字,用于支持安全的多线程编程。它对读/写变量施加了一些额外的条件。了解有关 volatile 变量的三件事很重要:

    1. volatile 变量的读/写操作是原子的。

    2. 一个线程将值写入 volatile 变量的操作结果对于使用该变量从中读取值的所有其他线程都是可见的。

    3. volatile 关键字禁用处理器和/或编译器中的某些优化/排列。

    那些。比较这些概念并不完全正确,因为 volatile 是执行安全多线程程序的词,而 false sharing 是描述性能下降的词。

    观看 Alexey Shipilev 关于Java 内存模型(不仅如此)的精彩讲座,他在其中将所有内容都安排得井井有条。

    如果您有任何疑问,我可以尝试通过更新我的答案来公开。

    UPD:以下问题的答案。

    规范中哪里写到 volatile 保证我们操作的原子性?

    链接:Oracle Essentials,规范

    这不就是我们设置synchronize来防止非原子性后果的原因吗?如果volatile保证原子性,那么他一个人就能解决所有问题,到底行不行?

    了解线程安全有两个方面很重要:(1) 执行控制和 (2) 内存可见性。第一个负责控制代码的执行(包括指令的顺序)和允许/禁止程序的某些块并发(concurrently/simultaneously)执行。第二个是什么内存操作对其他线程可见或不可见。这是因为每个处理器在处理器本身和共享内存之间都有多级缓存,所以运行在不同处理器内核上的线程由于处理器的本地缓存可以同时看到“不同的内存”。

    同步的

    使用synchronized可以防止另一个进程获取同一对象上的监视器(或锁) ,从而防止并发(同时)执行包含在同步块中的代码。重要的是要注意同步会创建所谓的先行关系。这种关系允许已获取监视器的线程在获取和释放监视器之前“查看”另一个线程所做的所有更改。实际上,这将(大致)对应于处理器将在捕获监视器时更新缓存并在释放后写入内存的事实。这些操作相当长(相对)。

    易挥发的

    volatile 的使用迫使人们使用程序的内存对变量执行操作,“绕过”处理器的缓存。当我们需要这个变量在不同线程中的可见性,但我们不关心访问这个变量的顺序时,这会很有用。同样在 32 位 java 上,当将变量声明为 volatile 时,long & double entry 变为 atomic。在新的 JSR-133 规范中(在 Java5 中),volatile 的语义得到了加强。对它施加了可见性规则和禁止某些编译器/jvm 优化的规则。

    例子

    挥发性 - 会有帮助

    假设我们有某种不可变对象,许多线程都可以引用它,并且它们在计算中不断使用它。Volatile 非常适合这种情况。有必要让其他线程在声明新对象后立即开始使用它(此时我的意思是我们会将引用从现有对象更改为新构造的对象)。同时,我们不需要专门同步这个更新,重置缓存。

    挥发性 - 无济于事

    让我们以常规计数器为例:

    volatile int counter = 0;
    
    public void update() {
        counter++; //или counter = counter + 1;
    }
    

    自增操作是非原子的,由三个操作组成:读取、自增、写入。在此示例中,可能会出现以下情况:

    • Thread1:进入方法读取值“0”;
    • Thread1:将值递增一个“1”;
    • 执行移至第二个线程;
    • Thread2:读取值“0”;
    • Thread2:将值增加一个“1”;
    • Thread2:写“1”到计数器;
    • 执行跳转到第一个线程;
    • Thread1:写“1”给counter;

    结果,计数器中存储的不是值“2”,而是值“1”。在这种情况下,方法update()或使用的同步AtomicInteger等将有所帮助。这超出了这个问题的范围。

    总结以上所有内容 - 当对象发生的所有操作都是“原子”时使用 volatile 变量,如第一个示例(对完全形成的对象的引用发生变化,记录来自一个线程)并且没有竞争对象的状态。

    • 26
  2. etki
    2020-07-25T12:15:54Z2020-07-25T12:15:54Z

    我会发布一些说明。

    据我所知,这个问题本身听起来有点不同:“如果缓存一致性协议要求处理器缓存以一致的状态存储内存位置,那么为什么我们需要 volatile,它做同样的事情?”

    首先,有两个不同层次的讨论。JLS 在 JVM 内部运行,缓存一致性协议仅存在于特定的处理器架构中。编译和运行 JVM 的体系结构上不必存在缓存一致性。,因此 JLS 使可选特性成为强制性的(事实上,该特性只不过是一致性,更多内容见下文)。我很确定 99% 以上的多核处理器现在都有这个协议,但是 Java 不能依赖任何不能保证的东西——所有 Java 应用程序都应该在所有架构上运行相同(除了与例如,可能存在不同路径的操作系统)。因此,JLS 几乎不得不引入这样一个概念,即使它存在于大多数系统上,因为即使 JVM 是用某种 python 实现的,它仍然必须按照与 on 相同的方式执行代码任何其他系统。

    其次,如果我们采用维基百科的定义:

    如果对同一内存位置的所有写入都按某种顺序执行,则多处理器是缓存一致的

    (意译)如果对同一地址的所有写入都按某种顺序执行,则多处理器缓存是一致的

    那么在这里你应该注意“内存位置”。Java 中有一些数据类型可以占用处理器运行的多个字 - 至少,当在 32 位操作系统上运行时,double 和 long 将分别占用两个字。如果我理解正确,那么在这样的系统上可能会出现以下情况:

    линия кэша 1: <другие данные><старшие или младшие 32 бита double>
    линия кэша 2: <остаток double><другие данные>
    

    在这种情况下,即使在严格缓存一致性的条件下,处理器也有权更新 double 的一半,因此线程有权看到垃圾而不是实际值。volatile 禁止这种情况,保证任何变量记录的原子性。

    第三,除了直接“打铁”的问题外,编译器(间接)参与了代码的执行。我不知道这对现代 Java 有多适用,但是一个积极的编译器可以自由地应用以下优化:

    boolean flag = true;
    
    while (flag) {
        doProcessing();
    }
    
    // хм, flag не отмечен volatile, значит, программист считает, что он может обновляться только локально
    // закэширую-ка я его в регистре процессора, так будет быстрее
    
    eax = load(flag);
    while (eax) {
       doProcessing();
    }
    

    寄存器永远不会更新——它与缓存完整性协议无关。同样,我不知道现有 Java 编译器的实际行为如何,但这个特定示例在 JLS 中列为不安全。

    最后,volatile 的语义会干扰程序执行的顺序。JLS 需要满足以下条件:

    • 同一个线程中的所有操作都具有先行发生的依赖关系 - 即 代码中父操作的结果将始终对代码中的子操作可见。
    • 所有具有 volatile 的操作都相互依赖 happens-before - 如果有人将值写入 volatile 字段,则所有后续读取都不再有权查看过时的值
    • happens-before 关系是可传递的,即 如果操作 A happens-before B,并且 B happens-before C,则 A happens-before C 为真,这意味着 C 将看到 A 所做的所有更改。

    只要满足这些条件,编译器、JVM 和处理器就可以自由移动表达式。如果我们采用以下代码

    int result = 0;
    boolean done = false;
    
    ....
    
    this.result = 1;
    this.done = true;
    

    那么他完全有权变成

    this.done = true;
    this.result = 1;
    

    因为所有后续表达式仍然会看到相同的结果。在这个例子中,另一个已经看到done = true的线程仍然可以从 中读取 0 result。但是,如果声明done为volatile,那么写入result必须发生在写入之前true,done读取发生在写入true之后,这样可以保证监听线程的变化可见。这并没有消除在此期间结果中出现多个条目的可能性,它仅保证在读取结果时,它将具有与完成更新同时的值或较晚的值。

    更新

    除了以上所有,还有一个搞笑的案例。坦率地说,Java 的臀部很大,更准确地说,是在这样的臀部上的 GC 执行时间。自然地,他们尝试在与应用程序并行工作的 GC 的帮助下处理这个问题。在这种情况下,其中一种策略是从正在清除的区域中疏散活动对象,以便简单地声明它是免费的以进行完全覆盖。在这种情况下,对象的两个副本(一个在旧地址,另一个 - 被疏散)可以同时存在于 JVM 中,这需要同步记录和读取。对于实施者来说幸运的是,JMM 不对常规读取做出任何承诺,因此大多数操作都可以从同步中释放出来,并且在某一时刻可能会发生所有写入都转到一个对象,而读取是从另一个对象完成的情况- 只要访问不同步。与上述所有示例一样,这与缓存一致性完全一致,但在应用程序运行时允许异常(出于相同的原因 - 缓存一致性在各个内存块、JVM - 对象和字段级别工作)。本段指的是 Shenandoah GC,这在第十次 java 中是预期的,但在其他情况下可以安全地预期这种射击腿的方式。

    • 18
  3. Akzhan Abdulin
    2020-07-26T02:25:37Z2020-07-26T02:25:37Z

    关于虚假分享,他们上面写的非常正确。

    一般来说,有一个简单的规则,如果有一个作者和许多读者,volatile 非常适合。

    如果有很多编写器,则需要原子操作或其他同步原语。

    • 4

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    Python 3.6 - 安装 MySQL (Windows)

    • 1 个回答
  • Marko Smith

    C++ 编写程序“计算单个岛屿”。填充一个二维数组 12x12 0 和 1

    • 2 个回答
  • Marko Smith

    返回指针的函数

    • 1 个回答
  • Marko Smith

    我使用 django 管理面板添加图像,但它没有显示

    • 1 个回答
  • Marko Smith

    这些条目是什么意思,它们的完整等效项是什么样的

    • 2 个回答
  • Marko Smith

    浏览器仍然缓存文件数据

    • 1 个回答
  • Marko Smith

    在 Excel VBA 中激活工作表的问题

    • 3 个回答
  • Marko Smith

    为什么内置类型中包含复数而小数不包含?

    • 2 个回答
  • Marko Smith

    获得唯一途径

    • 3 个回答
  • Marko Smith

    告诉我一个像幻灯片一样创建滚动的库

    • 1 个回答
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Алексей Шиманский 如何以及通过什么方式来查找 Javascript 代码中的错误? 2020-08-03 00:21:37 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5