Подтвердить что ты не робот

С# Требуется блокировка при замене ссылки на переменные в многопоточном приложении

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

private List<string> _list = new List<string>();
private void UpdateList()
{
    var newList = new List<string>(QueryList(...));
    _list = newList;
}

private void ThreadRun()
{
    foreach (var item in _list)
    {
        // process item...
    }
}

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

4b9b3361

Ответ 1

Чтобы убедиться в том, что вам не нужна ни тщательность, ни оптимизация, используйте Interlocked.Exchange() для обновления поля. Тогда у вас будет соответствующий барьер памяти при записи, без расхода volatile на каждый прочитанный.

Ответ 2

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

Ваш код в порядке, но вы должны рассмотреть маркировку _list как volatile. Это будет означать, что все чтения получают последнюю версию, а компилятор/джиттер/процессор не будут оптимизировать прочтение.

В качестве альтернативы вы можете поместить Thread.MemoryBarrier() непосредственно перед тем, как назначить новый список, чтобы убедиться, что все записи в новый список были зафиксированы до его публикации, но это не проблема архитектуры x86.

Ответ 3

Назначение является атомарным, ваш код в порядке. Возможно, вы захотите рассмотреть маркировку _list как volatile, хотя для обеспечения того, чтобы любые потоки, запрашивающие переменную, получили самую последнюю версию:

private volatile List<string> _list = new List<string>();

Ответ 4

Если вы используете .NET 4 или +, вы можете использовать новые новые потокобезопасные коллекции...

BlockingCollection<string> list  = new BlockingCollection<string>();

Класс BlockingCollection имеет поточно-безопасные методы для добавления и удаления элементов, скорее, как шаблон дизайна производителя.

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

Кроме того, он позволяет делать такие вещи...

    foreach (string i in list)
    {
        list.Take();
        list.Add(i + 200);
    }

Этот код удаляет и добавляет в коллекцию, пока ее перечислитель работает, что никогда не может быть сделано в С# до .NET 4. Нет необходимости объявлять его изменчивым.

    foreach (string i in list)
    {
        new Task(() =>list.Take()).Start();
        new Task(() =>list.Add(i + 200)).Start();
    }

В этом фрагменте запускаются нити N * 2, которые работают в одном списке...

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

Ответ 5

Вам нужно прочитать модель памяти .net, так как в целом порядок прав не определен на процессоре, поэтому новое значение _list может быть записано в общую память, а часть нового созданного объекта указана to _list все еще находится в очереди на запись процессоров.

Переопределение прав - это боль, о которой нужно подумать, поэтому я хотел бы вставить Thread.MemoryBarrier(); как это.

private void UpdateList() 
{     
    var newList = new List<string>(QueryList(...));   
    Thread.MemoryBarrier();  
    _list = newList; 
} 

Подробнее см. Threading на веб-странице С# Джозефом Альбахари.

Однако я думаю, что на большинстве "нормальных" процессоров ваш код будет работать как написанный.