gpt4 book ai didi

multithreading - 为什么 `Rc`线程不安全?

转载 作者:行者123 更新时间:2023-12-03 11:27:15 24 4
gpt4 key购买 nike

我正在阅读The Rust Programming Language的chapter 16 — Shared-State Concurrency。它说:

Unfortunately, Rc<T> is not safe to share across threads. When Rc<T>manages the reference count, it adds to the count for each call toclone and subtracts from the count when each clone is dropped. But itdoesn’t use any concurrency primitives to make sure that changes tothe count can’t be interrupted by another thread.


这是什么意思

to make sure that changes tothe count can’t be interrupted by another thread.


我认为唯一的中断计数更改的情况是是否创建了线程,并且以某种方式 panic /崩溃,因此该锁定器从未解锁,因此引用计数也从未减少。我可以想象,如果发生 panic ,Rust会调用作用域中每个对象的析构函数。
有人可以帮我澄清一下吗?

最佳答案

to make sure that changes to the count can’t be interrupted by another thread.


这是非常不幸的措辞,而且是不准确的。确实,打扰是我们最不担心的事情。
就并发而言,Rust的内存模型基于C11和C++ 11所采用的内存模型。如果您想了解有关内存模型的更多信息,我只建议阅读Preshing在 Weak vs Strong memory models上的文章。在这个答案中,我将尽力做到物质公正。
您问什么是内存模型?
粗略地说,内存模型是一个模型,该模型指定哪些操作可以重新排序,哪些不能重新排序。
重新排序可能会发生:
  • 在优化器中。
  • 在CPU中。

  • 通常,出于性能原因,对读/写进行重新排序是很好的。它可以更有效地利用CPU并加快进度。但是,某些算法的正确性取决于以某种顺序观察事件的各种线程……因此,某些读/写有时不应重新排序。内存模型和内存顺序用于指定编译器和CPU为正确执行算法而应遵循的确切约束。
    CPU如何破坏 Rc
    通过忽略增量。
    在弱存储器模型中,如果两个不同的内核使计数器递增,则增量之一可能会被忽略。
    想象以下时间轴,在给定线程上,其中CN表示当前所有者数为N,而C0表示销毁。
     T1 -- Create: C1 --- Clone: C2 -- Drop Clone: C1 --- Drop: C0.
    现在,假设该线程共享 Rc:
     T1 -- Create: C1 --- Clone: C2 ---------------C1---- Drop Clone: C0 --- Access **BOOM**.
    \ /
    T2 \_ Clone: C2 -- Drop Clone: C1 _/
    ^ ^
    Only one increment was seen But both decrements are
    为什么CPU会这样做?
    表现。
    强大的内存模型意味着内核之间有很多不必要的颤动来同步高速缓存行-颤动会增加操作的延迟。
    较弱的内存模型可以减少颤动,从而减少延迟,这意味着可以更快地执行程序或以更低的功耗执行程序。
    如果内存模型足够强大?
    即使在假设每个读取/写入都涉及内存的CPU上,由于竞争条件,它仍然可能出错。
    具体来说:
  • T1读取计数(1),T1计算递增的计数2,T1写入计数(2)。
  • T2读取计数(1),T2计算递增的计数2,T2写入计数(2)。

  • 如果查看Rust中的 AtomicXXX类型,您会注意到存在许多RMW(读-修改-写)操作,例如 fetch_add,这些操作原子地读取,递增和写入。
    原子性很重要,否则可能会发生竞争条件。
    优化器如何打包 Rc
    即使在没有任何寄存器的假设CPU上,增量/减量将直接原子地修改内存,但事情仍然可能出错。
    允许优化器假定在没有内存排序的情况下,没有其他执行线程在观察对内存的写操作:毕竟,这是未定义行为。
    因此,可以完美地允许优化程序执行以下操作:
  • 创建Rc的副本。
  • 删除原始文件。
  • 递减计数器(-2)-融合递减的乐趣和利润!
  • 使用克隆。
  • 递增计数器(+1)。
  • 删除克隆。

  • 如果另一个线程删除了(3)和(5)之间的最后一个其他引用,则计数器将达到0,因此另一个线程将在内部删除该值。
    我不确定我是否理解...
    不用担心,您不必!
    Rust编译器支持您。除非您发出 unsafe,否则将确保您不会意外引入此类比赛条件。
    至于了解所有这些,那里有很多文献。排序的确切效果是 documented,对于普雷欣(Preshing)的整体而言,真的很不错,我衷心推荐他们的博客。

    关于multithreading - 为什么 `Rc`线程不安全?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62483454/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com