Функция InterlockedExchange
Я работаю со списком, который разделен между многими потоками.
Я считаю, что для хорошей производительности в этом случае будет полезно использовать функцию InterlockedExchange для вставки данных в этот список, но у меня есть некоторые сомнения.
Если поток пытается прочитать переменную, которая записывается другим потоком с InterlockedExchange, какова будет реакция? Поток, который читает переменную, будет ожидать завершения записи или продолжит работу и не сможет прочитать переменную?
необходимо использовать InterlockedExchange для чтения переменной, когда она записывается с помощью interlockedechange?
Как проверить эту функцию, чтобы узнать, какова будет реакция на множественный доступ к общей переменной между потоками?
2 ответа
Краткий ответ: InterlockedExchange
вряд ли вам поможет. Но важно понимать, почему это так, поэтому вы сможете лучше понять ситуации, в которых это может помочь.
Почему одновременное чтение и запись является проблемой?
Во-первых, обратите внимание, что любое количество потоков может считывать одни и те же данные одновременно, не беспокоясь. Мы только начинаем беспокоиться, как только один поток может писать одновременно. Теперь нет ничего изначально рискованного в написании самой. Проблема возникает, когда даже крошечная логика принятия решения применяется к совместно используемым данным, используемым одновременно.
Например, у вас может быть список. Один поток (писатель) удаляет элемент из списка, в то время как другие потоки читают элементы в списке. Если вам не повезло со сроками выполнения ваших операций, читатель может подтвердить, что элемент существует в то же время, когда автор удаляет его. И затем, когда читатель пытается использовать недействительный элемент, вы в лучшем случае получаете нарушения прав доступа, а в худшем - повреждение данных.
Как защитить данные
Самый простой способ защитить данные (без архитектурных изменений) - это ввести механизм блокировки / блокировки форм. Прежде чем один поток запускает критическую операцию, он говорит: "Я занят общими данными, все остальные потоки должны подождать, пока я закончу, если они хотят использовать данные".
Обратите внимание, что упрощенный подход создает другие проблемы:
- Производительность может быть значительно снижена, если многие потоки проводят большую часть времени в ожидании или "борьбе за блокировки".
- Проблемы с управлением блокировками могут привести к тому, что два потока будут блокировать друг друга, что приведет к зависанию системы (так называемая взаимоблокировка)
(ПРИМЕЧАНИЕ. Существует множество альтернативных способов защиты ваших данных, но это выходит за рамки этого поста.)
InterlockedExchange
Эта рутина и другие Interlocked***
Братья и сестры предоставляют упрощенный механизм блокировки по шагам базовой, общей операции записи. ПРИМЕЧАНИЕ. Это все еще механизм блокировки, как описано выше, и разделяет большинство его проблем.
Двухступенчатая операция, которая InterlockedExchange
защищает это:
- прочитайте предыдущее значение и
- напишите новое значение.
Причина, по которой это может нуждаться в защите, заключается в том, что если два потока одновременно обмениваются общим значением, существует вероятность противоречивого поведения.
Например, заданное начальное значение равно A. Поток № 1 обменивается, устанавливая значение на B. Поток № 2 обменивается, устанавливая значение на C. Если потоки выполняются одновременно с обработкой #1 незначительно раньше, чем #2, возможны 2 результата.
- С блокировкой № 2 будет заблокировано до тех пор, пока #1 не закончится, и окончательный результат всегда будет: Значение установлено на C. Поток #1 имеет ссылку на A. Поток # 2 имеет ссылку на B.
- Без блокировки мы обычно получим те же результаты, что и выше, но есть вероятность, что мы этого не сделаем. Окончательный результат может быть: Значение установлено в C. Поток #1 имеет ссылку на A. Поток # 2 имеет ссылку на
A <-- Discrepancy
,
Это не всегда будет проблемой, но в некоторых случаях это может быть. В таком случае InterlockedExchange
служит простейшим механизмом защиты на основе блокировки.
Почему InterlockedExchange вряд ли будет работать для вас
Вы сказали, что ваши данные были в списке, но не указали, какого рода список; так что я предполагаю стандартный Delphi TList
, Вставка элемента в список не является простой внутренней операцией.
- Внутренний счетчик поддерживается автоматически.
- Существующие элементы могут быть перемещены.
- Необходимо проверить текущую емкость списка и, возможно, увеличить ее.
NB! Примечание. Даже если вы используете структуру данных списка, которая сама по себе может извлечь выгоду из InterlockedExchange
Есть еще другие проблемы, о которых вам нужно знать.
У вас есть два набора данных здесь. Есть внутренние данные структуры списка (вы можете не думать о ней как о таковой, но она есть). А потом есть данные ваших реальных записей.
Даже после защиты структуры списка, если у вас есть потоки, которые могут одновременно обновлять ваши записи, у вас есть потенциальная проблема. Вы должны защищать свои данные, а не просто добавлять их в коллекцию. Это означает, что вам может понадобиться механизм блокировки, охватывающий оба набора данных; и нет никаких шансов, что какой-либо из основных, общих Interlocked***
рутины достигнут этого.
Если поток пытается прочитать переменную, которая записывается другим потоком с InterlockedExchange, какова будет реакция? Поток, который читает переменную, будет ожидать завершения записи или продолжит работу и не сможет прочитать переменную?
Чтение чтения будет читать либо:
- Значение, хранящееся в памяти перед выполнением операции записи, или
- Значение, сохраненное после выполнения операции записи.
Фактически это верно для простого чтения / записи, то есть если вы не используете атомарные функции. Доступ к памяти данных машинного размера (или меньше) является атомарным. То есть вы гарантированно не имеете частичного чтения или прав. Должно быть так, что ваша переменная выровнена, так как вы используете InterlockedExchange. Ergo, не может быть частичного чтения или записи, и, следовательно, не может быть разрыва.
Теперь, если ваша переменная не была выровнена, тогда гонка данных может привести к тому, что поток чтения получит часть значения до записи и часть значения после записи. Это называется разрывом.
Необходимо ли использовать InterlockedExchange для чтения переменной, когда она записывается с InterlockedExchange?
На самом деле это не сработает. Потому что InterlockedExchange изменяет переменную. И операция чтения не делает. Прочитайте значение с простым чтением из памяти. Это единственный способ. Конечно, у вас есть гонка данных с потоком записи, но это неизбежно.
У меня есть серьезные сомнения, что ваш код правильно реализует контейнер без блокировки. Вставка предмета в свободный от блокировок контейнер нелегко реализовать. На самом деле контейнеры без блокировки очень сложны в реализации. Вам нужно больше, чем просто вызовы InterlockedExchange, когда вы изменяете контейнер.