Форум программистов «Весельчак У»
  *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

  • Рекомендуем проверить настройки временной зоны в вашем профиле (страница "Внешний вид форума", пункт "Часовой пояс:").
  • У нас больше нет рассылок. Если вам приходят письма от наших бывших рассылок mail.ru и subscribe.ru, то знайте, что это не мы рассылаем.
   Начало  
Наши сайты
Помощь Поиск Календарь Почта Войти Регистрация  
 
Страниц: [1]   Вниз
  Печать  
Автор Тема: Потокобезопасный доступ к коллекции  (Прочитано 18894 раз)
0 Пользователей и 1 Гость смотрят эту тему.
Gadget
Участник

ru
Offline Offline
Пол: Мужской

« : 22-11-2012 10:33 » 

нужно создать коллекцию, доступ к элементам которой будет потокобезопасным. Желательно, чтобы запись и чтение могли производиться из любого потока. В моем распоряжении второй фрэймворк.
я в десятый раз перечитываю мануалы о потокобезопасности и операторе lock, но до конца понять сокральный смысл этой высокой материи не могу.
В общем, я создаю некий класс, производный от ArrayList (т.к. у него есть член SyncRoot и всякие стандартные фишки добавления-удаления-перечисления) и пытаюсь его обезопасить (пока только для чтения).

Код: (C#)
    public class SafeList<T>: ArrayList
    {
        public SafeList() : base() { }
        public SafeList(int capacity) : base(capacity) { }

        public new T this[int index]
        {
            get { return (T)base[index]; }
            set { base[index] = value; }
        }
        /// <summary>
        /// Выполняет поиск элемента, удовлетворяющего условиям указанного предиката, и возвращает первое
        /// найденное вхождение в пределах всего списка
        /// </summary>
        /// <param name="predicate">Делегат Predicate(T), определяющий условия поиска элемента</param>
        /// <returns>Первый элемент, удовлетворяющий условиям указанного предиката, если такой элемент найден;
        /// в противном случае — значение по умолчанию для типа T</returns>
        public T Find(Predicate<T> predicate)
        {
            lock (SyncRoot)
            {
                foreach (T item in this)
                {
                    if (predicate(item))
                        return item;
                }
            }
            return default(T);
        }
        /// <summary>
        /// Извлекает все элементы, удовлетворяющие условиям указанного предиката
        /// </summary>
        /// <param name="predicate">Делегат Predicate(T), определяющий условия поиска элементов</param>
        /// <returns>Список SafeList(T), содержащий все элементы, удовлетворяющие условиям указанного предиката,
        /// если такие элементы найдены; в противном случае — пустой список SafeList(T)</returns>
        public SafeList<T> FindAll(Predicate<T> predicate)
        {
            SafeList<T> found_ = new SafeList<T>();
            lock (SyncRoot)
            {
                foreach (T item in this)
                {
                    if (predicate(item))
                        found_.Add(item);
                }
            }
            return found_;
        }
        /// <summary>
        /// Выполняет указанное действие с каждым элементом списка
        /// </summary>
        /// <param name="action">Делегат Action(T), выполняемый для каждого элемента списка</param>
        public void ForEach(Action<T> action)
        {
            lock (SyncRoot)
            {
                foreach (T item in this)
                {
                    action(item);
                }
            }
        }

Возникает вопрос. От чего конкретно блокируется коллекция? Если у меня один поток будет читать коллекцию, то все остальные желающие почитать будут стоять в очереди?
и еще.  если я хочу добавить или удалить элемент, мне делать это с самой коллекцией или с ее потокобезопасной оболочкой (которая возвращается методом Synchronized())?
а если я ничего не буду добавлять/удалять, а просто захочу изменить какое-нибудь поле одного из элементов коллекции, мне тоже обращаться к Synchronized()?
Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #1 : 22-11-2012 11:11 » 

Gadget, lock создаёт критическую секцию по объекту, указанному в параметре. SyncRoot тут вообще необязателен - достаточно lock(this) - это будет эквивалентом модификатора synchronized в Java.

Поскольку это критическая секция, то доступ в неё потоков происходит строго поочередно. Чтение там или запись - это уже твои собственные особенности. Важно не вызывать методы с lock(this) друг из друга, чтобы не возник deadlock. Весь доступ только через обёртку. Хотя бы потому, что если один поток читает коллекцию через enumerator, а другой в это время потокобезопасно пишет в неё, enumerator сломается, и читающий поток упадёт.

Если нужен 1 писатель и много читателей, в System.Threading есть соответствующие классы для такого рода блокировок.


Вообще "потокобезопасная коллекция" - это не постановка задачи; нужно точно знать, где и для чего эта коллекция будет использоваться, чтобы выбрать оптимальное решение.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Gadget
Участник

ru
Offline Offline
Пол: Мужской

« Ответ #2 : 22-11-2012 11:27 » 

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

Добавлено через 13 минут и 41 секунду:
если один поток читает коллекцию через enumerator, а другой в это время потокобезопасно пишет в неё, enumerator сломается, и читающий поток упадёт.
а если один читает коллекцию через оператор foreach, предварительно ее заблокировав, а второй, тоже заблокировав, пишет (не в оболочку, а в саму коллекцию), foreach умрет?

Добавлено через 40 минут и 8 секунд:
И, кстати, какую роль тогда во всем этом великолепии играет член SyncRoot класса ArrayList?
Я правильно понимаю, что в приведенном коде будет корректнее исправить во всех методах, где используется перебор элементов,  foreach (T item in this) на foreach (T item in Synchronized(this))?
« Последнее редактирование: 22-11-2012 12:44 от gadjet » Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #3 : 22-11-2012 13:01 » 

Gadget, разумеется foreach умрёт, и с потоками это вообще не связано. Достаточно попробовать добавить или удалить изнутри foreach, как немедленно будет получена ошибка.

Что такое Synchronized(this)?
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Gadget
Участник

ru
Offline Offline
Пол: Мужской

« Ответ #4 : 22-11-2012 13:13 » 

Что такое Synchronized(this)?
"Возвращает синхронизированную (потокобезопасную) оболочку списка" (с)MSDN
Synchronized() может возвратить 2 типа объекта, в зависимости от того, что получит в качестве входного параметра. Если параметр типа ArrayList, то и вернет он ArrayList, если IList, то вернет он соответственно IList. а так как у моего SafeList базовым является ArrayList, то Synchronized(this) вернет оболочку типа ArrayList
http://msdn.microsoft.com/ru-ru/library/dcaa7ckt.aspx

Добавлено через 4 минуты и 45 секунд:
разумеется foreach умрёт
А если всё тоже самое, только писать в оболочку, то foreach будет жив?
Извини, что столько дурацких вопросов, просто хочу расставить для себя все точки.
« Последнее редактирование: 22-11-2012 13:17 от gadjet » Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #5 : 22-11-2012 14:48 » 

Gadget, если Synchronized такое делает, зачем ты вообще весь огород городишь?

Что такое "писать в оболочку"? Оболочка-то над чем?
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Gadget
Участник

ru
Offline Offline
Пол: Мужской

« Ответ #6 : 24-11-2012 11:36 » 

Это я как раз и хочу понять...
Записан
Gadget
Участник

ru
Offline Offline
Пол: Мужской

« Ответ #7 : 26-11-2012 15:38 » 

Цитата: MSDN
В следующем примере показано, как заблокировать коллекцию на все время перечисления с помощью свойства SyncRoot.
Код: (C#)
ArrayList myCollection = new ArrayList();

lock(myCollection.SyncRoot)
{
    foreach (object item in myCollection)
    {
        // Insert your code here.
    }
}
Допустим, что myCollection у меня не локальная переменная, а член какого-нибудь класса.
Во время вышеуказанного перечисления в Потоке1, я в Потоке2 создам экземпляр оболочки и запишу в эту оболочку что-нибудь.
Код: (C#)
ArrayList mySyncdAL = ArrayList.Synchronized( myCollection );
mySyncdAL.Add(new object());
foreach в этом случае не умрет? И, если он не умрет, значит, изменения в коллекции должны вступить в силу после выхода из блокировки в Потоке1?
« Последнее редактирование: 26-11-2012 15:40 от Gadget » Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #8 : 26-11-2012 17:39 » 

Gadget, напиши foreach, а внутри add - в одном потоке. И посмотри, что будет. Твой вопрос вообще к потокам отношения не имеет, а имеет он отношение к объекту типа enumerator и его состоянию.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Gadget
Участник

ru
Offline Offline
Пол: Мужской

« Ответ #9 : 27-11-2012 12:42 » 

Dimka, я прекрасно понимаю, что будет, если изменить список во время перечисления. просто мне не понятен принцип работы этой растреклятой оболочки. как, используя ее, обезопасить перечисление коллекции? Обезопасить именно от изменений, внесенных из других потоков
Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #10 : 27-11-2012 12:54 » new

Gadget, возьми и проверь. Я сомневаюсь, что она блокирует коллекцию на время существования enumerator'ов. А вот нужно ли тебе такая блокировка, или ты готов создать собственный enumerator, устойчивый к изменениям коллекции - кто ж кроме тебя знает?!
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Страниц: [1]   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines