1

第一件事:

  • 我知道什么Span<T>Memory<T>是什么

  • 我知道为什么Span<T>必须只驻留在堆栈上

  • 我(概念上)知道什么是结构撕裂

我还不清楚的是:结构撕裂不是问题Memory<T>吗?据我了解,基本上所有大于 WORD 大小的类型都可以/将受此影响。更进一步,当这种类型可以在多线程读写器场景中使用时,它可能会导致竞争条件,如下面的链接所述。

直截了当地说:这个例子Memory<T>使用而不是Span<T>

internal class Buffer {
    Memory<byte> _memory = new byte[1024];

    public void Resize(int newSize) {
        _memory = new byte[newSize]; // Will this update atomically?
    }

    public byte this[int index] => _memory.Span[index]; // Won't this also possibly see partial update?
}

根据CoreFX 的实现, Memory<T>还依次布置了一个(托管对象)引用、它的长度和一个索引。Span<T>我失踪的区别在哪里,Memory<T>适合那些场景?

4

1 回答 1

2

通过阅读中的评论Memory<T>,它看起来绝对可以被撕裂。

但是,似乎有两个地方实际上很重要:Memory<T>.Pin()Memory<T>.Span.

需要注意的重要一点是(据我所知)我们不关心以某种方式撕裂,这意味着我们仍然指向我们所引用的对象中的某个位置——尽管我们的调用者可能会得到一些奇怪的数据没想到,这是安全的,因为他们不会得到 AccessViolationException。由于对字段进行不同步的线程访问,它们只会有一个产生意外结果的竞争条件。


Memory<T>.Span从. Span<T>_ Memory<T>它有这样的评论

如果 Memory 或 ReadOnlyMemory 实例被破坏,则此属性 getter 具有未定义的行为。我们尝试检测这种情况并抛出异常,但有可能一个撕裂的结构在我们看来是有效的,我们将返回一个不希望的跨度。与原始 Memory 实例相比,这样的跨度始终保证至少在边界内,因此使用跨度不会 AV 进程。

所以,我们绝对可以有一个撕裂Memory<T>的,然后尝试从中创建一个Span<T>。在这种情况下,在代码中有一个检查,如果它Memory<T>以这样一种方式被撕裂,它现在引用了Memory<T>.

如果它以某种方式撕裂,它仍然引用原始对象中的某个位置,那没关系 - 我们的调用者可能没有读取它期望读取的内容,但至少他们不会得到 AccessViolationException,这就是我们试图避免。

请注意,Span<T>无法实现相同的检查(即使它想要)。Memory<T>保留对对象、起始偏移量和长度的引用。Span<T>仅保留对对象内部某些内存地址和长度的引用。


Memory<T>.Pin()是一种unsafe方法,它有这样的评论

如果结构被撕裂,以下逻辑可能会导致 AV。这没关系,因为调用者希望使用原始指针,我们不需要像其他基于 Span 的 API 一样保持它的安全。

同样,我们可以以一种不再引用我们所指对象内部某处的方式撕裂。但是这种方法是unsafe,我们不在乎。

于 2019-02-26T17:53:55.087 回答