Почему блокировка при чтении из словаря

Я смущен листингом кода в книге, которую я читаю, C# 3, короче говоря на поточной обработке. В теме на Потокобезопасности в Серверах приложений, ниже кода дан как пример UserCache:

static class UserCache
{
    static Dictionary< int,User> _users = new Dictionary< int, User>();

    internal static User GetUser(int id)
    {
        User u = null;

        lock (_users) // Why lock this???
            if (_users.TryGetValue(id, out u))
                return u;

        u = RetrieveUser(id); //Method to retrieve from databse

        lock (_users) _users[id] = u; //Why lock this???
            return u;
    }
}

Авторы объясняют, почему метод RetrieveUser не находится в блокировке, это должно постараться не блокировать кэш в течение более длительного периода.
Я смущен относительно того, почему блокировка, TryGetValue и обновление словаря с тех пор даже с выше словаря обновляются дважды, если 2 потока звонят одновременно с тем же неполученным идентификатором.

Что достигается путем блокировки чтения словаря?
Большое спасибо заранее за все Ваши комментарии и понимание.

9
задан Svish 9 August 2010 в 12:12
поделиться

4 ответа

Dictionary класс не является потокобезопасным .

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

Таким образом, код использует блокировку для предотвращения одновременной записи.

16
ответ дан 4 December 2019 в 10:02
поделиться

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

чтение в словарь необходимо заблокировать, потому что другой поток может писать в то же время, и при чтении необходимо выполнить поиск по согласованной структуре.

Обратите внимание, что ConcurrentDictionary , представленный в .NET 4.0, в значительной степени заменяет этот вид идиомы.

4
ответ дан 4 December 2019 в 10:02
поделиться

Это обычная практика для доступа к любым не потокобезопасным структурам, таким как списки, словари, общие разделяемые значения и т.д.

И отвечая на главный вопрос: блокируя чтение, мы гарантируем, что словарь не будет изменен другим потоком, пока мы читаем его значение. В словаре это не реализовано, поэтому он и называется непотокобезопасным :)

.
1
ответ дан 4 December 2019 в 10:02
поделиться

Если два потока вызывают одновременно и идентификатор существует, то они оба вернут правильную информацию о пользователе. Первая блокировка предназначена для предотвращения ошибок, как сказал SLaks - если кто-то пишет в словарь, пока вы пытаетесь его прочитать, у вас будут проблемы. В этом случае вторая блокировка никогда не будет достигнута.

Если два потока вызывают одновременно и идентификатор не существует, один поток блокируется и вводит TryGetValue, это вернет false и установит u в значение по умолчанию. Эта первая блокировка снова предназначена для предотвращения ошибок, описанных SLaks. В этот момент этот первый поток снимет блокировку, а второй поток войдет и сделает то же самое. Оба затем установят 'u' на информацию из 'RetrieveUser (id)'; это должна быть та же самая информация. Затем один поток заблокирует словарь и присвоит _users [id] значение u. Эта вторая блокировка заключается в том, что два потока пытаются одновременно записать значения в одни и те же ячейки памяти и повреждают эту память.Я не знаю, что сделает второй поток, когда войдет в задание. Он либо вернется раньше, игнорируя обновление, либо перезапишет существующие данные из первого потока. Тем не менее, Dictionary будет содержать одинаковую информацию, потому что оба потока должны были получить одни и те же данные в формате u от RetrieveUser.

Что касается производительности, автор сравнил два сценария: описанный выше сценарий, который будет крайне редким и будет блокироваться, пока два потока попытаются записать одни и те же данные, и второй сценарий, в котором гораздо более вероятно, что два потока вызывают запрос данных для объект, который нужно написать, и тот, который существует. Например, threadA и threadB вызывают одновременно, а ThreadA блокируется для несуществующего идентификатора. Нет причин заставлять threadB ждать поиска, пока threadA работает с RetriveUser. Эта ситуация, вероятно, гораздо более вероятна, чем повторяющиеся идентификаторы, описанные выше, поэтому для повышения производительности автор решил не блокировать весь блок.

0
ответ дан 4 December 2019 в 10:02
поделиться
Другие вопросы по тегам:

Похожие вопросы: