NET, Огляд багатопоточності

Для багатопоточності існує кілька причин. Припустимо, у додатку робиться звернення до якогось сервера в мережі, яке може зайняти певний час. Навряд чи захочеться, щоб інтерфейс користувача через це блокувався, і користувачеві довелося просто чекати моменту, коли від сервера повернеться відповідь. Користувач може виконувати в цей час якісь інші дії або взагалі скасувати надісланий серверу запит. У таких ситуаціях застосування багатопоточності приносить користь.

Для всіх видів активності, що вимагають очікування, наприклад через отримання доступу до файлу, бази даних або мережі, може запускатися новий потік, що дозволяє виконувати в той же час інші завдання. Багатопотоковість може допомогти, навіть якщо є лише насичені в плані обробки завдання. Численні потоки того самого процесу можуть одночасно виконуватися різними ЦП або, що частіше зустрічається в наші дні, різними ядрами одного багатоядерного ЦП.

Зрозуміло, необхідно знати особливості одночасного виконання множини потоків. Через те, що вони виконуються в один і той же час, при отриманні ними доступу до тих самих даних можуть виникати проблеми. Щоб цього не відбувалося, мають бути реалізовані механізми синхронізації.

У додатку, що функціонує на сервері, один потік завжди чекає надходження запиту від клієнта і тому називаєтьсяпотоком-слухачем (listener thread). При отриманні запиту він відразу ж пересилає його окремому робочому потоку (worker thread), який далі сам продовжує взаємодіяти з клієнтом. Потік-слухач після цього негайно повертається до своїх обов'язків щодо очікування надходження наступного запиту чергового клієнта.

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

Це дозволяє потокам всередині одного процесу швидко взаємодіяти між собою, оскільки всі потоки процесу звертаються до однієї і тієї ж віртуальної пам'яті. Однак це також ускладнює справу, оскільки дає можливість безлічі потоків змінювати ту саму область пам'яті.

Основи багатопотокової обробки

Розрізняють два різновиди багатозадачності: на основі процесів та на основі потоків. У зв'язку з цим важливо розуміти різницю між ними. Процес відповідає за управління ресурсами, до яких належить віртуальна пам'ять і дескриптори Windows, і містить щонайменше один потік. Наявність хоча б одного потоку є обов'язковою для виконання будь-якої програми. Тому багатозадачність на основі процесів - це засіб, завдяки якому на комп'ютері можуть паралельно виконувати дві програми і більше.

Відмінності в багатозадачності на основі процесів і потоків можуть бути зведені до наступного: багатозадачність на основі процесів організується для паралельного виконання програм, а багатозадачність на основі потоків - для паралельного виконання окремих частин однієї програми.

Головна перевага багатопотокової обробки полягає в тому, що вонадозволяє писати програми, які працюють дуже ефективно завдяки можливості вигідно використовувати час простою, що неминуче виникає під час виконання більшості програм. Як відомо, більшість пристроїв введення-виведення, будь то пристрої, підключені до мережних портів, накопичувачі на дисках або клавіатура, працюють набагато повільніше, ніж центральний процесор (ЦП). Тому більшу частину свого часу програмі доводиться чекати надсилання даних на пристрій введення-виведення або прийому інформації з нього. А завдяки багатопотоковій обробці програма може вирішувати якесь інше завдання під час вимушеного простою.

Наприклад, в той час як одна частина програми надсилає файл через з'єднання з Інтернетом, інша її частина може читати текстову інформацію, що вводиться з клавіатури, а третя — здійснювати буферизацію чергового блоку даних, що надсилаються.

У середовищі .NET Framework визначено два різновиди потоків:пріоритетний тафоновий. За замовчуванням потік, що створюється, автоматично стає пріоритетним, але його можна зробити фоновим. Єдина відмінність пріоритетних потоків від фонових полягає в тому, що потік фону автоматично завершується, якщо в його процесі зупинені всі пріоритетні потоки.

У зв'язку з організацією багатозадачності на основі потоків виникає потреба в особливий режим, який називається синхронізацією і дозволяє координувати виконання потоків цілком певним чином. Для такої синхронізації C# передбачена окрема підсистема.

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

У мові C# та середовищі .NET Frameworkпідтримуються обидва різновиди багатозадачності: на основі процесів та на основі потоків. Тому засобами C# можна створювати як процеси, так і потоки, а також керувати тими та іншими. Для того щоб розпочати новий процес, від програмуючого потрібно зовсім небагато зусиль, оскільки кожен попередній процес цілком відокремлений від наступного.

Набагато важливішою є підтримка C# багатопоточної обробки, завдяки якій спрощується написання високопродуктивних, багатопоточних програм на C# в порівнянні з деякими іншими мовами програмування.

Класи, що підтримують багатопотокове програмування, визначені в просторі імен System.Threading . Тому будь-яка багатопотокова програма на C# включає наступний рядок коду:

Простір імен System.Threading містить різні типи, що дозволяють створювати багатопотокові програми. Мабуть, головним серед них є клас Thread , оскільки він представляє окремий потік. Щоб програмно отримати посилання на потік, що виконується конкретним екземпляром, просто викличте статичну властивість Thread.CurrentThread:

На платформі .NET немає прямої відповідності "один до одного" між доменами додатків (AppDomain) і потоками. Фактично певний AppDomain може мати кілька потоків, що виконуються у кожний момент часу. Більш того, конкретний потік не прив'язаний до одного домену програм протягом свого часу існування. Потоки можуть перетинати межі доменів додатків, коли це заманеться планувальнику Windows та CLR.

Хоча активні потоки можуть перетинати межі AppDomain, кожен потік у кожний конкретний момент часу може виконуватися лише всередині одного домену додатків (іншими словами,неможливо, щоб один потік працював більш ніж в одному домені додатків відразу). Щоб програмно отримати доступ до AppDomain, де працює поточний потік, викличте статичний метод Thread.GetDomain():

Єдиний потік також у будь-який момент може бути переміщений у певний контекст, і він може переміщатися в межах нового контексту примхи CLR. Для отримання поточного контексту, в якому виконується потік, використовуйте статичну властивість Thread.CurrentContext (яка повертає об'єкт System.Runtime.Remoting.Contexts.Context):

Ще раз: за переміщення потоків між доменами додатків та контекстами відповідає CLR. Як розробник .NET, ви завжди залишаєтеся у щасливому невіданні щодо того, коли завершується кожен конкретний потік (або куди саме його буде поміщено після переміщення). Тим не менш, корисно знати про різні способи отримання примітивів, що лежать в основі.