Підрозділ 15.Q
Питання для самоконтролю
Питання для самоконтролю — Розділ 15. Багатопоточність 15.1. Введення у багатопоточність. Клас Thread 1. Що таке процес і що таке потік? Яке ключове ресурсне розмежування між ними? Чому кілька потоків одного пр
Питання для самоконтролю — Розділ 15. Багатопоточність
15.1. Введення у багатопоточність. Клас Thread
Що таке процес і що таке потік? Яке ключове ресурсне розмежування між ними? Чому кілька потоків одного процесу можуть ефективно обмінюватись даними, але це водночас породжує небезпеки?
Поясніть, чому властивість
Thread.Nameможна присвоїти лише один раз. Яке виключення кинеться при повторному присвоєнні, і чому це обмеження зроблено навмисно?Що виведе наступний код — і чому результат може відрізнятися при кожному запуску?
Thread t = new Thread(() => Console.WriteLine("Worker")); t.Start(); Console.WriteLine("Main");Порівняйте передній (foreground) і фоновий (background) потоки. Напишіть приклад, де неправильний вибір типу потоку може призвести до незбереження важливих даних при завершенні програми.
Метод
Thread.Sleep(0)іThread.Sleep(100)— у чому принципова різниця між ними? Коли доцільно використовуватиSleep(0)?Який стан набуває потік після виклику
Join()на ньому іншим потоком? Що станеться, якщо викликатиJoin()на потоці, що вже завершився?Є код:
Thread t = new Thread(() => { Thread.Sleep(5000); Console.WriteLine("Done"); }); t.IsBackground = true; t.Start(); Console.WriteLine("Main ends");Чи виведеться рядок
"Done"? Обґрунтуйте відповідь.Спроєктуйте систему первинного прийому пацієнта, де паралельно виконуються: вимірювання показників (передній потік), завантаження медичної картки (передній потік) і архівація старих записів (фоновий потік). Поясніть, чому архівація є фоновим потоком, а не переднім.
15.2. Створення потоків. ThreadStart та ParameterizedThreadStart
Що таке делегат
ThreadStart? Як він відрізняється відParameterizedThreadStart? Запишіть їх сигнатури.Знайдіть і виправте помилку в наступному коді:
for (int i = 0; i < 3; i++) { new Thread(() => Console.WriteLine(i)).Start(); }Що виведеться без виправлення і чому? Як правильно?
Чому у
ParameterizedThreadStartпараметр має типobject?а не конкретний тип? Які два способи безпечно привести цей параметр до конкретного типу, і який з них кращий? Поясніть різницю між(T)paramіparam as T.Порівняйте три способи передачі коду у потік: іменований метод, анонімний метод і лямбда-вираз. Для якого сценарію ви б обрали кожен? Наведіть конкретний приклад.
Напишіть код, що запускає 4 потоки паралельно — по одному на кожне лабораторне замовлення з масиву
LabOrder[]— і чекає завершення всіх перед тим, як вивести «Всі аналізи готові».Що означає рядок
string patient = patients[i]перед захопленням у лямбду в циклі? Що відбудеться, якщо замість копії захопитиpatients[i]напряму черезi?Коли доцільно створювати клас-обгортку для передачі даних у потік через
ParameterizedThreadStart? Наведіть приклад такого класу для клінічного сценарію.
15.3. Синхронізація потоків. Оператор lock та клас Lock
Що таке стан перегонів (race condition)? Чому операція
count++є небезпечною при паралельному доступі, якщо виглядає як одна операція?Що виведе наступний код після завершення всіх потоків — і чи буде відповідь стабільною?
int n = 0; Thread[] threads = new Thread[3]; for (int i = 0; i < 3; i++) threads[i] = new Thread(() => { for (int j = 0; j < 1000; j++) n++; }); foreach (var t in threads) t.Start(); foreach (var t in threads) t.Join(); Console.WriteLine(n);Чому не можна використовувати рядковий літерал або
thisяк об'єкт-замок уlock? Яка стандартна практика для оголошення об'єкта-замка?Поясніть, що відбувається «під капотом», коли компілятор обробляє конструкцію
lock (obj) { ... }. Напишіть еквівалентний код черезMonitor.Enter/Monitor.Exit.Що таке взаємне блокування (deadlock)? Напишіть мінімальний приклад двох потоків, що гарантовано утворять дедлок, і поясніть, як його уникнути.
Порівняйте
volatile,Interlockedіlock: що гарантує кожен засіб і яка його вартість? Для яких конкретних сценаріїв підходить кожен?Клас
Lock(.NET 9) надає методEnterScope(). Чому патернusing (_lock.EnterScope())надійніший заlock (_lock) { ... }з точки зору обробки виключень? Або вони еквівалентні — обґрунтуйте.Напишіть потокобезпечний клас
BedManager, що управляє кількістю вільних ліжок у лікарні (операціїAdmit()іDischarge()), використовуючиlockдля захисту спільного стану.
15.4. Клас Monitor
У чому принципова різниця між
Monitorі операторомlock? В яких трьох ситуаціях варто обиратиMonitorзамістьlock?Поясніть три дії, що виконує
Monitor.Wait(obj)атомарно. Чому умова перевіряється у цикліwhile, а не вif? Що таке «хибне пробудження» (spurious wakeup)?Чим відрізняється
Monitor.Pulse(obj)відMonitor.PulseAll(obj)? В якому сценарії потрібенPulseAll? Наведіть клінічний приклад.Де в коді можна викликати
Monitor.Wait(),Monitor.Pulse()іMonitor.PulseAll()? Яке виключення виникне при порушенні цієї вимоги?Знайдіть проблему в наступному коді:
object _lock = new object(); Queue<string> _queue = new Queue<string>(); void Consumer() { lock (_lock) { if (_queue.Count == 0) Monitor.Wait(_lock); Console.WriteLine(_queue.Dequeue()); } }Чому
ifтут небезпечний? Як виправити?Реалізуйте патерн «виробник — споживач» для лабораторної черги: медсестра додає зразки, лаборант забирає. Використовуйте
Monitor.WaitіMonitor.Pulse. Забезпечте коректне завершення при порожній закритій черзі.Monitor.TryEnter(obj, 200)повернувfalse. Що це означає? Яку альтернативну логіку може виконати потік замість нескінченного очікування?
15.5. Клас AutoResetEvent
Що таке
AutoResetEvent? Яка ключова особливість відрізняє його відManualResetEvent? Поясніть аналогію з турнікетом.Що відбудеться, якщо викликати
Set()наAutoResetEvent, коли жоден потік не чекає наWaitOne()? Чи «накопичується» сигнал?Порівняйте
AutoResetEventзMonitor.Wait/Monitor.Pulse. Яка ключова відмінність у вимогах щодо спільного об'єкта-замка?Напишіть сценарій конвеєра із трьох кроків (реєстрація → огляд → аналізи), де кожен крок запускається в окремому потоці і чекає сигналу від попереднього через
AutoResetEvent. Поясніть, чому всі потоки запускаються одночасно, але виконуються послідовно.Що повертає
WaitOne(int milliseconds)при успішному і при тайм-аутовому завершенні? Напишіть приклад використання цього варіанту для обмеження часу очікування відповіді.Поясніть різницю між
new AutoResetEvent(false)іnew AutoResetEvent(true). В якому сценарії корисна початково сигнальна версія?Клінічна система чекає підтвердження від лаборатоії. Якщо підтвердження не надійде протягом 3 секунд — надіслати нагадування адміністратору. Реалізуйте цю логіку через
WaitOneз тайм-аутом.
15.6. Клас Mutex
Яка ключова властивість
Mutexвідрізняє його відlock? У яких двох конкретних сценаріях слід обиратиMutex?Що означає «реентерабельність» (recursive) Mutex? Що станеться, якщо потік, що тримає Mutex, викличе
WaitOne()ще раз? І яке зобов'язання це накладає?Поясніть призначення параметра
out bool createdNewу конструкторі іменованого Mutex. Як за допомогою цього параметра реалізувати «singleton-процес» (тільки один запущений екземпляр)?Що означає префікс
Global\\в імені іменованого Mutex? Чим він відрізняється відLocal\\?Знайдіть потенційну проблему в наступному коді:
Mutex m = new Mutex(); m.WaitOne(); DoWork(); // може кинути виняток m.ReleaseMutex();Як виправити, щоб Mutex завжди звільнявся?
Що таке
AbandonedMutexException? Коли він виникає і яке його практичне значення для відновлення після збою попереднього власника ресурсу?Порівняйте
lockіMutexза продуктивністю, областю дії та можливістю тайм-ауту. Склавши зведену таблицю, обґрунтуйте вибір для кожного з двох сценаріїв: (а) захист спільного лічильника в одному процесі; (б) заборона запуску другого екземпляра генератора звітів.
15.7. Клас Semaphore та SemaphoreSlim
Що таке семафор? Поясніть аналогію зі стоянкою з N місцями. Чим він відрізняється від
Mutex/lock?Порівняйте
SemaphoreіSemaphoreSlim: за продуктивністю, підтримкоюasync/await, можливістю іменування. Коли слід обирати кожен?Що означає
new SemaphoreSlim(0, 10)? Що відбудеться з потоками, що викличутьWait(), доки не буде викликаноRelease()?Напишіть код для системи, де 8 пацієнтів паралельно намагаються потрапити до 3 кабінетів УЗД. Забезпечте, щоб у будь-який момент в кабінетах перебувало не більше 3 пацієнтів. Розмістіть
Release()у правильному місці.SemaphoreSlim.Release(5)— що означає передача числового аргументу? У якому сценарії (виробник пакетно публікує завдання) це корисно?Знайдіть проблему в коді:
SemaphoreSlim sem = new SemaphoreSlim(3, 3); sem.Wait(); ProcessData(); // може кинути виняток sem.Release();Як виправити?
Спроєктуйте систему обмеження навантаження (rate limiting) для SMS-розсилки: шлюз дозволяє максимум 5 паралельних запитів. Реалізуйте через
SemaphoreSlim, пояснивши логіку ініціалізації і де розміщуєтьсяWait()/Release().Склавши зведену таблицю всіх примітивів синхронізації розділу (
lock,Monitor,AutoResetEvent,Mutex,Semaphore,SemaphoreSlim), відповідайте на три питання для кожного: скільки потоків пропускає одночасно, чи підтримує міжпроцесну синхронізацію, який основний сценарій використання.