Является ли struct tearing проблемой для Memory <T>? - PullRequest
0 голосов
/ 26 февраля 2019

Перво-наперво:

  • Я знаю, что Span<T> и Memory<T>

  • Я знаю, почему Span<T> должен проживатьтолько в стеке

  • Я (концептуально) знаю, что такое разрыв структуры

Что мне остается неясным: Разрыв структуры также не являетсявопрос для Memory<T>?Насколько я понял, это может повлиять на каждый тип больше WORD-size.Более того, когда такой тип может использоваться в многопоточном сценарии чтения-записи, это может привести к условиям гонки, как описано в ссылке ниже.

Чтобы добраться до сути: не будет этопример также поднимает проблему потенциально несовместимого объекта 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> подходящим для этих сценариев?

1 Ответ

0 голосов
/ 26 февраля 2019

Из прочтения комментариев в Memory<T> похоже, что его можно разорвать.

Однако, похоже, есть два места, где это действительно имеет значение: Memory<T>.Pin() и Memory<T>.Span.

Важно отметить, что (насколько я могу понять) мы не заботимся о разрыве таким образом, что означает, что мы все еще указываем на где-то в объекте, к которому мы обращаемсяк - хотя наш абонент может получить некоторые странные данные, которых он не ожидал, это безопасно в том смысле, что они не получат AccessViolationException.У них просто будет состояние состязания, которое приводит к неожиданным результатам, вследствие наличия несинхронизированного многопоточного доступа к полю.


Memory<T>.Span получает Span<T> от Memory<T>.У него есть этот комментарий :

Если экземпляр Memory или ReadOnlyMemory порван, это средство получения свойства имеет неопределенное поведение.Мы пытаемся обнаружить это условие и генерируем исключение, но возможно, что порванная структура может показаться нам действительной, и мы вернем нежелательный диапазон.Такой промежуток всегда гарантированно находится в границах по сравнению с исходным экземпляром Memory, поэтому использование промежутка не приведет к AV-процессу.

Таким образом, мы можем абсолютно разорваться Memory<T>, а затем попробуйте создать Span<T> из него.В этом случае в коде есть проверка, которая выдает исключение, если Memory<T> разрывается таким образом, что теперь он ссылается на некоторую память вне объекта, на который ссылается Memory<T>.

Если он порвался так, как он все еще ссылается где-то в исходном объекте, то это нормально - наш вызывающий может не читать то, что ожидал прочитать, но, по крайней мере, он не получит исключение AccessViolationException, что и являетсямы пытаемся избежать.

Обратите внимание, что Span<T> не может выполнить эту же проверку (даже если бы захотел).Memory<T> хранит ссылки на объект, начальное смещение и длину.Span<T> сохраняет только ссылки на некоторый адрес памяти внутри объекта и его длину.


Memory<T>.Pin() - это метод unsafe, который имеет этот комментарий :

Возможно, что приведенная ниже логика может привести к AV, если структура порвана.Это нормально, так как вызывающий объект ожидает использовать необработанные указатели, мы не обязаны сохранять его таким же безопасным, как и другие API на основе Span.

Опять же, мы можем порвать таким образом, что мыбольше не ссылаются где-то внутри объекта, на который мы ссылаемся.Однако этот метод unsafe, и нам все равно.

...