2/6/2008 9:50:21 PM

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

public sealed class TestObject    
{    
    public IEnumerable<string> Users { get; set; }
}

Какого рода действия здесь может предпринять программист для того, чтобы чувствовать себя уверенно в многопоточной среде?
Самое простое и часто распространенное - синхронизация. При этом, понятное дело, нужно синхронизировать как операции чтения, так и операции записи. То есть, если коллекцию кто-то пишет, то ее нельзя читать. А если коллекцию кто-то читает, то ее нельзя писать. Тогда случае самого примитивного lock'а мы сводим "на нет" всю нашу многопоточность: одновременно с коллекцией может работать только один поток, тот, который захватил этот самый монитор (lock).

Можно придумать более хитроумный подход, когда мы позволяем читать коллекцию многопоточно, а блокировать коллекцию будем только в случае ее изменения. Естественно, до начала внесения изменений все, кто читал коллекцию должны завершить свои операции (те, кто еще не начал - ждут), затем мы вносим изменения, а затем снова разрешаем операции чтения.
Подобные механизмы обычно реализуются через две очереди, одна содержит операции чтения и задачи из нее выполняются во многих потоках, вторая - операции записи, задачи из которой выполняются эксклюзивно. Такой подход легко реализуется через, скажем, CCR, а так же специальная инфраструктура для этого имеется в свободно распространяемой библиотеке Wintellect Power Threads, написанной Джефри Рихтером.
Однако и в случае этого подхода нас ожидает ряд сюрпризов. Один из них - продолжительность операции чтения коллекции. Представьте, что енумерируя TestObject.Users я для каждого элемента коллекции делаю запрос к веб-сервису. Допустим, я получаю данные по пользователю, обрабатываю их, вношу изменения в соответствующие статистические данные, делаю биллинг и т.д. Операции-то небыстрые. Я енумерирую, а все остальные операции просто стоят и ждут. Потому, что следующая на очереди - операция изменения коллекции. Она ждет, пока я закончу читать, остальные операции чтения ждут, пока операция записи выполнит свою работу эксклюзивно. Семеро одного ждут.
Да, я могу вместо енумерирования Users каждый раз создавать на его основе новую коллекцию, или лист, и потом тормозить с ней столько времени, сколько хочу, не мешая другим. Но - я вынужден буду делать это каждый раз, когда я хочу работать с коллекцией Users. Чтобы не заставлять ждать других, я каждый раз при чтении создаю новую копию коллекции. Плюс мне, человеку, в общем-то постороннему, нужно знать и помнить, что работать надо так и только так. Плюс - достаточно сложная инфраструктура с задачами, очередями... Точнее, минусы все это, в общем-то ;)

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

Comments (11) -

2/9/2008 12:09:26 PM

ДИВАН

Ход мысли автора мне понравился, очень даже не плохо, 5 ему=)

ДИВАН Russia

2/9/2008 11:46:06 PM

kastex

А я чуть по другому проблему обошел. Без очередей.
А меня простоянно несколько потоков модифицируют коллекции и их же читают.
Сделал примерно так:
public class SafeList<T>
{
private List<T> Data = new List<T>();

// добавить/ удалить - все обычно
public void Add(T Item){ lock (this.Data){ this.Data.Add(Item); }  }
public void Delete(T Item){ lock (this.Data){IsDeleted = this.Data.Remove(Item);}}

// Взять на чтение - вернем копию коллекции.
public T[] SyncronizedItems
{
    get { return this.Data.ToArray(); }
}
}

т.е. на чтение отдается 'слепок' коллекции на момент обращения. Уж как с ним клинет работать будет - его проблемы. Но все проблеммы с потоками прошли.
Про производительность не говорю - думаю, копейки.

Вот что меня удивило в свое время - List.ToArray() не дает проблем с потоками.
Можно бы реализацию его посмотреть, но что-то не получается в студии для List<> подцепить исходники.


kastex

2/9/2008 11:49:09 PM

kastex

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

kastex

2/9/2008 11:58:39 PM

kastex

PS. правда еще кое-что забыл.
должно быть что-то типа

public class SafeList<T> where T : IState
{
...
}

interface IState
{
ObjectState State { get{...}, set{}  }
}

enum ObjectState{ Enabled, Deleted };

Типа если клиент решит объект из коллеции попользовать, не мешало бы проверить его State - а вдруг его другой поток уже удалил? А в SafeList<> при удалении помечать что State теперь Deleted.

В принципе сей трикс простой, но решил 75% проблем с потоками и колекциями. Зато остальные 25% ... Smile

kastex

2/10/2008 12:03:25 AM

kastex

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

Перечитал пост.
В принципе, я написал что же, что ты и говорил.

kastex

2/10/2008 1:39:27 AM

Alexey Raga

Ну, у тебя ToArray (читай, копия коллекции) делается каждый раз при чтении. А я предлагал - при изменении.
Проблема тут еще в том, что ты в твоем случает делаешь lock при изменении, да, ты не допустишь параллельной записи из нескольких потоков, избежишь исключения.
Но, все же, излишнее копирование при каждом чтении - раз, "грязное чтение" - два. Ну, представь, что мне нужно вставить в лист 19 объектов. 7 я уже вставил, а тут из другого потока бац - ToArray, клиент получает "недовставленный" вариант.
В моем случае такого не будет ;) Как только я довставляю то, что мне нужно, я подменю коллекцию и с этого момента клиент получит все 19 разом ;)

Alexey Raga

2/11/2008 11:29:00 AM

Vlad

Очень принципиально не люблю такими способами отбоходить !
нужно советы у kastex брать !

Vlad Russia

2/11/2008 4:32:31 PM

kastex

в принципе согласен

kastex

9/19/2008 4:01:08 PM

Дмитрий

Алексей, все понравилось, но можно уточнить, что такое CCR аббревиатура?
Почему не упомянули multiple readers - single writer?

Дмитрий Russia

9/22/2008 2:34:51 PM

Дмитрий

Алексей, вы не могли бы пояснить аббревиатуру CCR?

Дмитрий Russia

7/28/2009 2:44:46 PM

Alexey Raga

CCR = Concurrency and Coordination Runtime.
Я писал несколько постингов о нём: http://alexey.raga.name/search.aspx?q=CCR

Alexey Raga Australia

Comments are closed

Powered by BlogEngine.NET 2.5.0.6

About the author

Alexey Raga Alexey Raga
.NET software developer.

E-mail me Send mail

Twitter


Recent posts

Archive

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2012

Sign in