Неизменяемые структуры данных и параллелизм
Я пытаюсь понять, как использование неизменяемых структур данных в параллельном программировании может устранить необходимость блокировки. Я читал кое-что в интернете, но пока не видел конкретных примеров.
Например, предположим, что у нас есть некоторый код (C#), который использует lock(s) вокруг Dictionary< string, object> делает это:
class Cache
{
private readonly Dictionary<string, object> _cache = new Dictionary<string, object>();
private readonly object _lock = new object();
object Get(string key, Func<object> expensiveFn)
{
if (!_cache.ContainsKey("key"))
{
lock (_lock)
{
if (!_cache.ContainsKey("key"))
_cache["key"] = expensiveFn();
}
}
return _cache["key"];
}
}
Как бы это выглядело, если бы _cache было неизменным? Можно ли удалить lock , а также убедиться, что expensiveFn не вызывается более одного раза?
3 ответов:
Короткий ответ состоит в том, что это не так, по крайней мере, не полностью.
Неизменяемость гарантирует только то, что другой поток не сможет изменять содержимое вашей структуры данных во время работы с ней. Как только у вас есть экземпляр, этот экземпляр никогда не может быть изменен, поэтому вы всегда будете в безопасности, читая его. Любые изменения потребуют создания копии экземпляра, но эти копии не будут напрямую вмешиваться в уже упомянутые экземпляры.
Их еще много причины, по которым вам понадобятся конструкции блокировки и синхронизации в многопоточном приложении, даже с неизменяемыми объектами. Они в основном имеют дело с проблемами, связанными со временем, такими как условия гонки или управление потоком потоков, чтобы действия происходили в нужное время. Неизменяемые объекты на самом деле ничего не сделают, чтобы помочь с такого рода проблемами.
Неизменяемость облегчает многопоточность , но не делает еелегкой .
Насколько ваш вопрос о том, как будет выглядеть неизменяемый словарь. Я должен сказать, что в большинстве случаев в вашем примере не имеет смысла даже использовать неизменяемый словарь. Поскольку он используется как "активный" объект, который по своей сути изменяется по мере добавления и удаления элементов. Даже в языке, построенном вокруг неизменяемости, как F#, есть изменяемые объекты для этой цели. Смотрите эту ссылку для получения более подробной информации. Неизменяемые версии можно найти здесь.
Основная идея сокращения неизменяемых структур данных (обратите внимание, что я сказал "сокращение", а не "устранение") необходимость блокировки в параллелизме заключается в том, что каждый поток работает либо с локальной копией, либо против неизменяемой структуры данных, поэтому нет необходимости в блокировке (ни один поток не может изменять данные других потоков, только свои собственные). Блокировка необходима только тогда, когда несколько потоков могут изменять одно и то же изменяемое состояние одновременно, потому что в противном случае у вас есть возможность "грязного чтения" и других аналогичный вопрос.
Один пример того, почему важны неизменяемые данные: Предположим, что у вас есть объект person, к которому обращаются два разных потока. Если thread1 сохраняет человека в карте (хэш человека содержит имя человека), то другой thread2 изменяет имя человека. Теперь thread1 не сможет найти этого человека внутри карты, пока он на самом деле там!
Если персона была неизменной, ссылки, удерживаемые разными потоками, будут разными, и thread1 сможет найти персону в карта даже тогда, когда пользователь 2 меняет свое имя (так как будет создан новый экземпляр человека).
Comments