Thread-safe структури даних. NET 4 (ч. 1)

. NET 4 містить багатий набір засобів, що спрощують розпаралелювання коду. Якщо ми починаємо обробляти деякий набір даних одночасно в декількох потоках, то автоматично піднімається питання про синхронізацію виконання цих потоків, зокрема про те, де зберігати результати обробки. Існує достатньо способів координувати потоки між собою, і завжди можна реалізувати будь-який з них. Але творці Parallel Extensions вже подбали про це, і до складу. NET 4 був включений ряд "потокобезопасних" структур даних. Реалізований набір найбільш популярних колекцій, з якими я і пропоную ознайомитися.

1. Черга: ConcurrentQueue <T>
Цей клас є класичну чергу, працює за принципом FIFO, з тією лише різницею, що до неї можливий безпечний доступ з боку декількох потоків. Нової "паралельної" природі відповідає і набір методів – отримання елемента виробляється за допомогою виклику Try *:


Код:
ConcurrentQueue <int> queue = new ConcurrentQueue <int> ();
queue.Enqueue(10);

int t;
Console.WriteLine(queue.TryPeek(out t));
Console.WriteLine(queue.TryDequeue(out t));


Як можна здогадатися з назви, відрізняються ці виклики тим, що TryPeek () залишає елемент в черзі, а TryDequeue () витягує його. Обидва методи повертають false якщо елемент отримати не вдалося, інакше – True. Додавання елемента проводиться за допомогою методу Enqueue () – тут нічого особливого немає. За допомогою властивостей Count і IsEmpty можна дізнатися кількість елементів у черзі, і чи є вони взагалі.

2. Стек: ConcurrentStack <T>
Тут точнісінько так само, як і з чергою – маємо справу зі звичайним стеком, але наділеним можливістю конкурентного доступу. Крім принципу LIFO, що використовується в стеці, відмінною рисою його є можливість додавання і вилучення кількох елементів:


Код:
ConcurrentStack <int> stack = new ConcurrentStack <int> ();
stack.Push(10);
stack.PushRange(new int[] { 1, 2, 3, 4, 5 });
int t;
if (stack.TryPop(out t))
{
    Console.WriteLine(“Pop: ” + t);
}
if (stack.TryPeek(out t))
{
    Console.WriteLine(“Peek: ” + t);
}
int[] ts = new int[5];
int count;
if ((count = stack.TryPopRange(ts, 0, 3)) > 0)
{
    Console.WriteLine(“PopRange”);
    for (int i = 0; i < count; i++)
    {
        Console.WriteLine(ts[i]);
    }
}

Ось результат роботи цієї ділянки коду:



Методи TryPeek () і TryPop () повертають bool значення, а TryPopRange () – кількість витягнутих елементів. Можна покласти в стек відразу кілька елементів за допомогою виклику PushRange ().

3. Колекція: ConcurrentBag <T>
Являє собою неупорядковане сховище даних, і цим схоже на безліч, хоча відрізняється від нього тим, що може зберігати дублюються елементи. На відміну від попередніх структур не гарантується будь-якої порядок вилучення елементів. Це, напевно, найпростіша колекція з усього набору:


Код:
ConcurrentBag <int> bag = new ConcurrentBag <int> (new int [] {1, 1, 2, 3});
bag.Add(70);

int t;
bag.TryPeek(out t);
Console.WriteLine(t);

bag.Add(110);
Console.WriteLine();
for (int i = 0; i < 3; i++)
{
    bag.TryTake(out t);
    Console.WriteLine(t);
}


Цей шматочок коду дасть наступний висновок на консоль:



Дивлячись на результат, може скластися відчуття, що колекція слід принципом LIFO. Ще раз підкреслю, що це не гарантується. Ніякого певного порядку. Зверніть увагу на конструктор – є можливість задати початковий набір елементів. Те ж саме можна зробити при створенні черги і стека.

4. Словник: ConcurrentDictionary <TKey, TValue>
І тут проглядаються риси "старого знайомого" – так, це реалізація звичного Dictionary <TKey, TValue>, але з можливістю конкурентного доступу. Зрозуміло, наділення такими здібностями не пройшло дарма – трохи змінився звичний набір методів. Давайте його розглянемо, для цього створимо колекцію:


Код:
ConcurrentDictionary<string, string>
dict = new ConcurrentDictionary<string, string>();
dict.TryAdd(“name”, “OFC340”);
dict.TryAdd(“age”, “25”);dict.TryAdd(“age”, “25”);

Робота з додавання / зміни / видалення елементів проводиться за допомогою методів Try *, які повернуть true, якщо дія виконано пройшла успішно, інакше false. У даному випадку додавання значення з ключем "Age" буде в перший раз успішно, а в другій – ні, при цьому ніяких виключень згенеровано не буде. Наприклад, спроба отримати значення по ключу, якого немає у словнику:


Код:
string t = string.Empty;
Console.WriteLine(dict.TryGetValue(“nokey”, out t));

призведе лише до висновку на консоль рядка "False". Видалення елемента буде виглядати так:


Код:
Console.WriteLine(dict.TryRemove(“age”, out t));

За допомогою властивостей Values і Keys можна отримати актуальні на момент виклику колекції ключів і значень словника. На цьому вся специфіка "потокобезопасной" версії закінчується.

Як бачите, у реалізації розглянутих нами колекцій немає ніякої "мега"-функціональності, тільки необхідні речі. Мені подобаються такі рішення – коли будуються базові структури, нагромадження зайвих можливостей лише заважає. У разі необхідності завжди можна доповнити їх потрібним поведінкою.

Крім зазначених, в поточній версії. NET beta 1 є двозв'язній список ConcurrentLinkedList <T>. Однак, я не буду на ньому зупинятися, оскільки в MSDN нас дбайливо попередили: "ConcurrentLinkedList (of T) is planned to be removed prior to the final release of Visual Studio 2010. Please do not use this class ", тобто цей список буде виключений і у фінальній версії. NET 4.0 його не буде. Тому витрачати час на його розгляд не варто (хоча, дивитися там особливо нічого – "конкурентна" версія відомого LinkedList <T>).

Представлені вище 4 структури даних – найпростіші, і на них я завершу першу частину огляду. У другій частині мова піде про більш цікавому сховище – BlockingCollection <T>.


Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*