multithreading and parallelism
TRANSCRIPT
Multithreading and Parallelism
Use cases
- Масштабованість на multi-CPU машинах- Виконання швидше- Обробка більшої кількості запитів
- Приховування затримок (hiding latency)- Обчислення, доки чекаємо на IO
- Відгук UI (responsiveness)- «Усунення» блокуючих операцій
Threads
ProcessПроцес визначає virtual address space.Досягається ізоляція, оскільки- процеси не можуть напряму
адресувати пам’ять інших процесів.
Разом з тим, спільне використання:- спільна бібліотека може входити до адресного простору кількох процесів.
Процес не виконує код,а лише надає ресурси і контекст для
виконання потоків
Потоки (threads) виконують код
Потоки виконуються в межах процесуМають доступ до всього адресного простору процесу.Кожен потік має свій стек та значення регістрів CPU
Windows
- Багатозадачна ОС- Витісняюче планування на основі
пріоритетів- (preemptive scheduling, priority-based)
- Потік виконується на будь-якому CPU
- CPU завжди виконує той потік, який має найвищий пріоритет (і який готовий до виконання)
Планування (scheduling)- Потік виконується протягом сталого
періоду часу – кванту - Величина квантів може бути різна,
залежить від:- Конфіга системи (короткі, довгі)- Статуса процесу (активний, фоновий)
- По закінченню кванта ОС перевіряє, чи є готовий потік з таким же пріоритетом
- Як тільки з’являється потік з вищим пріоритетом, поточний потік витісняється- Навіть якщо його квант не закінчився
Переключення контексту
(context switching)Вибравши новий потік для виконання, ОС переключає контекст.Зберігається стан регістрів CPU + IP попереднього потоку.Відновлюються регістри CPU нового потоку.
Потік звільняє CPU, коли:
- Переходить в стан очікування самостійно- Диск, мережа, пам’ять (!),
синхронізація- Витісняється потоком з вищим
пріоритетом- Який закінчив щось очікувати (диск,
мережа,...)- Його пріоритет збільшився
- Завершився квант- Завершився потік
Динамічне підвищення пріоритету
- Після закінчення вводу/виводу- Після закінчення очікування- При пробудженні GUI потоків
через операції з вікнами- Готовий до виконання потік
довший час не має шансу виконатись
Також при цьому може збільшуватись квант.
Вибір CPU для потоку
- Вибирається процесор, який простоює
- Якщо немає вільного процесора – витіснення на ідеальному процесорі
Scheduling granularity
Windows планує потоки, не процеси
Наслідок:Процес А має 1 потікПроцес Б має 9 потоківПроцес Б отримає 90% процесорного часу(за умови однакового пріоритету потоків)
Threads in .NET
- Створення потоку:- new Thread(Action)
- Запуск:- Thread.Start()
- Очікування доки потік завершиться:- Thread.Join()
Thread pool
- Пул потоків виконує завдання використовуючи вже створений набір потоків
- Додання нового завдання:- ThreadPool.QueueUserWorkItem(delegat
e)
UI message loop- Один GUI потік обробляє повідомлення
з черги:- Clicks, repaints, mouse moves, key presses, …
- Події обробляються послідовно- Отже, якщо одна подія займає довгий
час, решта будуть чекати- Отже, довготривалі події ніколи не
повинні виконуватись в UI потоці- IO, важкі обчислення
Оновлення контролів
- Щоб змінити контрол, код повинен виконуватись в UI потоці
- Інші потоки можуть програмно додати повідомлення в чергу UI потоку
Demo
Task Parallel Library (TPL)
Since .NET 4.0http://msdn.microsoft.com/en-us/library/dd460717.aspxMSDN Parallel Computing Dev Center
Taskhttp://msdn.microsoft.com/en-us/library/system.threading.tasks.task.aspx
Абстрагує значення, яке буде* доступне в майбутньому.
Значення є результатом - Обчислення (CPU)- Вводу (IO)
Task є .NET реалізацією future and promise
Значення буде* доступне
- Можливо вже є доступне- Таск завершився до звернення за
результатом,- Також таск міг виконатись в цьому ж
потоці- Можливо не буде, бо станеться
помилка- Або обчислення буде відмінено
Створення і запускTask Task.Factory.StartNew(Action);Task<TResult> Task.Factory.StartNew(Func<TResult>);
Або Task constructor + Task.Start() method
ВиконанняTask – не потік. За замовчуванням таски виконуються потоками з thread poolНаслідок 1: таск може почати виконуватись не одразуНаслідок 2: довготривалий таск буде займати потік з thread pool (цей потік не зможе працювати над рештою задач)
Виконання довготривалого Task`а
Буде створено окремий потік
Task.Factory.StartNew(() => { … }, TaskCreationOptions.LongRunning);
Завершення, результат
- Task.Wait() // with optional timeout
- Task.Result
Потік заблокується, поки таск не завершиться
Очікування декількох тасків
Task.WaitAll(Task[]) // with optional timeout
Task.WaitAny(Task[])
Demo
Handle exceptions option 1Task<int> t = Task.Factory.StartNew(() => { ... throw new InvalidOperationException("error"); return 42; });
int result = t.Result;
Тут виникне Exception
AggregateException
AggregateException.InnerException(s) міститиме початковий exception
Handle exceptions option 2
Check Task state:- Task.IsCompleted- Task.IsFaulted- Task.Exception- Task.Status
AggregateException
Unhandled exceptionsЯкщо не обробити exception, TPL його перевикине при фіналізації Task’a
Викличеться подія TaskScheduler.UnobservedTaskException
Помилка вважається обробленою при:- Виклику Task.Result, Task.Wait() etc.- Звертанні до Task.Exception
Demo
Continuations
Задача: після завершення таска виконати наступний.В наступному таску – доступний результат попереднього.
Task<U> Task<T>.ContinueWith(antecedent => {...});
TaskFactory.ContinueWhenAll(Task[], tasks => {...});
Continuations, способи
- One-to-one- Many-to-one- One-to-many- Chain
Demo
Деякі приклади використання Tasks
- Досягнення паралелізму- Виконання CPU-bound задач
паралельно- Виконання IO-bound задач
паралельно- Responsive UI
Demo
Синхронізація
Data raceint count = 0;for (int i = 0; i < data.Length; i++){ var task = Task.Factory.StartNew(() => { count++; }); tasks.Add(task);}
Task.WaitAll(tasks.ToArray());
Not atomic!
Preemption
reg = read count (1)
inc reg (2)
write reg to count (2)
reg = read count (1)
inc reg (2)
write reg to count (2)
Thread B
Thread A
Рішення 1 – interlockedint count = 0;for (int i = 0; i < data.Length; i++){ var task = Task.Factory.StartNew(() => { Interlocked.Increment(ref count); }); tasks.Add(task);}
Task.WaitAll(tasks.ToArray());
Atomic,Flushes caches
Рішення 2 – locking (critical section)
int count = 0;object syncRoot = new object();for (int i = 0; i < data.Length; i++){ var task = Task.Factory.StartNew(() => { lock (syncRoot) { count++; } }); tasks.Add(task);}
Task.WaitAll(tasks.ToArray());
Other threads cannot
acquire held lock
Parallel LoopsParallel LINQ
Parallel.For() and ForEach()
Parallel.For(0, data.Length, i => { ProcessItem(data, i); });
Parallel.ForEach(data, value => { ProcessItem(value); });
Заблокується, доки всі ітерації не виконаються
Є можливість зробити Break, передавши state
Parallel LINQ (PLINQ)var result = from value in data.AsParallel() where value % 2 == 0 let cube = Math.Pow(value, 3) let processed = ProcessItem(value) select new { Cube = cube, Result = processed };
parallelResult.ToArray();
Виконання почнеться тут
Варто також дізнатись про:
- Task.Status, Task lifecycle http://blogs.msdn.com/b/pfxteam/archive/2009/08/30/9889070.aspx
- Continuations http://msdn.microsoft.com/en-us/library/dd235663
- TaskSchedulers http://msdn.microsoft.com/en-us/library/dd997402
- Cancellation http://msdn.microsoft.com/en-us/library/dd997364.aspx
- Synchronization primitives http://msdn.microsoft.com/en-us/library/ms228964.aspx
- Concurrent collections http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx
- Безплатна книжка, ще одна