Транзакционная память

В процессе подготовки материала к продолжению "Разбираемся с JBoss Transactions. Часть 1", пришлось как раз изучить, что такое эта транзакционная память - "Software transactional memory (Википедия, English)".
 Достаточно сказать, что она вошла в состав новейшего .NET 4, вполне достаточно, чтобы попробовать изучить.

Как явствует из Википедии (ссылка выше) - это модель работы с многопоточностью, применяемая часто в СУБД, которая коренным образом отличается от lock-модели.

Плюсы достаточно интересны - во-первых она более проста для понимания и использования, чем lock-модель. Во-вторых она может быть и более быстрой (не всегда, но при каких-то условиях и сильно еще зависит от конкретной реализации). В-третьих, по уверению авторов, она менее подвержена такому КРАЙНЕ нехорошему моменту в многопоточности, как инверсия приоритетов. О последнем могут не беспокоиться пользователи операционной системы реального времени QNX, а вот пользователи Windows большинства версий, Linux, FreeBSD - мягко говоря - "сосут лапу", их ОС не защищены от инверсии приоритетов в многопоточности. При определенных обстоятельствах поток с небольшим приоритетом может не получать возможность работы относительно долго, вполне достаточно для непредотвращения взрыва ядерного реактора ответственным за глушение его потоком выполнения.

Итак, чем же характеризуется модель многопоточного программирования "Транзакционная память". Модель очень проста:

1) Есть какое-то место в программе, разделяемое между несколькими потоками, какие-то данные, для краткости назовем их переменными "a" и "b".

2) Нужно обеспечить атомарность изменения переменных "a" и "b" разными потоками. Например поток 1-ый делает инкремент обеим переменным, 2-ой - декремент обеим этим переменным, при этом нужно чтобы потоки делали этим изменения последовательно, сначала один из них начал работу, закончил, только тогда второй поток может тоже начать изменять их.

3) Lock-модель делает блокировку этого места в коде. При этом только один поток работает с залоченными местом в коде, остальные потоки ждут, когда и им дадут возможность поработать с этим местом кода тоже. Транзакционная память делает иначе - она позволяет ВСЕМ получать доступ к этому месту кода. Фишка тут в следующем, - ПЕРЕД тем как начать изменять (или просто считывать) переменные "a" и "b" - выполняющийся поток увеличивает значение или изменяет флаг какой-то особой переменной, выделенной для этой цели - назовем эту переменную как "z". Смотрите - поток изменяет флаг в "z", после этого работает с участком кода, где переменные "a" и "b". ПОСЛЕ этого, как данный поток внес изменения в "a" и "b" - он проверяет значение флага в переменной "z". Если значение флага в "z" не изменилось, значит никто не "покусился" за это время на переменные "a" и "b". Если изменилось, значит транзакция нарушена и, что нужно сделать в таком случае? - правильно, откат транзакции! Поток еще раз пробует поставить значение флага "z" и внести изменения в переменные "a" и "b". Все очень зависит от процессора и подсистем памяти - вполне возможно, что многие потоки получат достаточно времени, что гарантированно внести изменения в это место - и успеть поставить флаг и поменять обе переменные, пока данный поток не будет вытеснен другим потоком.

И дело вот еще в чем, про это не знает абсолютное большинство Java-программистов - вы можете затипизировать переменные "a" и "b"  особыми атомарными типами, которые есть в Java. Да вот в чем незадача - атомарность эта достигается за счет полного блокирования системной шины. Ваша система может таким "колом" встать от этой атомарности, что подумайте - стоит ли применять. Вместе с тем ДАННАЯ модель многопоточного программирования - может использовать атомарность только для работы с переменной "z", а не с "a" и "b". В простейшем моем, сильно утрированном примере - вы всего 1 раз поставить на кол всю систему, а не 2 раза. Теперь "введите" десять потоков, которые доведут до ручки систему, если использовать "обычный" "атомарный" подход в Java.
Тем более при изменении таких нескольких переменных - помимо атомарности все равно нужно вводить синхронизацию на это место в коде, при lock-подходе.

Минусы:

1) Не возможно отменять большинство операций, связанных с I/O. Правда в некоторых реализациях транзакционной памяти применяют буферы, чтобы обойти этот момент.
2) Может быть высокий рост производительности (как и бОльшая понятность кода и более проще отладить), но это смотря сколько процессоров в вашей системе и еще некоторых факторов.

Более подробно вы можете изучить в Википедии - "Software transactional memory (English)"

Используется сокращение - STM (Software transactional memory).

3 коммент.:

  1. А теперь представьте, что вам нужно выполнять синхронизованно относительно длительную операцию. В случае блокировок потоки будут медленно, но верно ее выполнять, а в случае оптимистичных транзакций они будут только и делать что начинать все заново из-за вмешательства других потоков.

    Кроме того, значительно усложняется программирование - все вызываемые под блокировкой функции (а так же все, что вызывается из них и.т.д.) нужно делать transaction-aware, а они могут составлять значительную часть.

    Так что этот метод применим лишь к синхронизации на коротких участках кода. И в этом случае он действительно может быть панацеей от проблем синхронизации.

    ОтветитьУдалить
  2. При длительных транзакционных операциях - Lock-синхронизация тоже будет чревата.

    Получил один поток - объект-синхронизации, и удерживает его - длительное время. Другие потоки вообще не смогут этим участком кода воспользоваться, по моему получается - прямой аналог как и при транзакционной памяти, для таких ситуаций...

    С другой стороны - когда такие ситуации возможны? Оперативная обработка транзакций, OLAP, - это когда пользователь "интерактирует" с веб-интерфейсом, в конце взаимодействия или коммитит результат, или отменяет все (не решился сделать покупку в Интернет-магазине).

    Наверное получается выбор - или давать ОДНОМУ пользователю монопольную блокировку на используемое в транзакции им, или давать работать ВСЕМ, при этом надеяться, что кто-то не закоммитит, или может быть и многие не закоммитят и "единственно верный пользователь", начавший транакцию и в конце нажавший "Покупаю!" - получит свое, при этом доступ имеют и другие.

    Уменьшением "гранулированности" лоченного или транзакционной памятью обслуживаемоего - добиваемся большего успеха.

    Наверное так?

    ОтветитьУдалить
  3. Тут еще много моментов может быть - например в Java можно "попробовать" получить блокировку, а не прямо по ходу выполнения взять и заблокироваться на объекте-синхронизации. Есть специальная конструкция языка, для того, чтобы только ПОПРОБОВАТЬ получить.

    Если блокировку объекта синхронизации получить невозможно - можно предусмотреть другое выполнение (ветвление) кода. Возможно в каких-то ситуациях пригодится, такая возможность.

    ОтветитьУдалить