Хоча потоки можуть пришвидшити виконання коду, вони дійсно потрібні? Чи можна виконати кожен фрагмент коду за допомогою однієї нитки чи є щось таке, що може бути досягнуто лише за допомогою декількох потоків?
Хоча потоки можуть пришвидшити виконання коду, вони дійсно потрібні? Чи можна виконати кожен фрагмент коду за допомогою однієї нитки чи є щось таке, що може бути досягнуто лише за допомогою декількох потоків?
Відповіді:
Перш за все, потоки не можуть пришвидшити виконання коду. Вони не змушують комп'ютер працювати швидше. Все, що вони можуть зробити, - це підвищити ефективність роботи комп’ютера, використовуючи час, який інакше буде витрачено даремно. У деяких видах обробки ця оптимізація може підвищити ефективність та скоротити час роботи.
Проста відповідь - так. Ви можете написати будь-який код для запуску на одному потоці. Доказ: Єдина процесорна система може виконувати інструкції лише лінійно. Наявність декількох рядків виконання здійснюється операційною системою переривань, зберігаючи стан поточного потоку та починаючи ще один.
Комплекс відповідь ... складніший! Причина того, що багатопотокові програми часто можуть бути ефективнішими, ніж лінійні, - через апаратну "проблему". Процесор може виконувати обчислення швидше, ніж IO пам'яті та жорсткого зберігання. Так, наприклад, інструкція "додати" виконує набагато швидше, ніж "отримання". Кеші та вилучення спеціальних програмних інструкцій (не впевнений у точному терміні тут) можуть певною мірою боротися з цим, але проблема з швидкістю залишається.
Threading - це спосіб боротьби з цією невідповідністю за допомогою використання процесора для пов'язаних з процесором інструкцій під час завершення інструкцій IO. Типовим планом виконання потоків, ймовірно, був би: Вилучення даних, обробка даних, запис даних. Припустимо, що виймання та запис займають 3 цикли, а обробка займає один, для ілюстративних цілей. Ви можете бачити, що комп'ютер читає чи записує, він не робить нічого протягом 2 циклів кожен? Зрозуміло, що це лінь, і нам потрібно зламати наш оптимізаційний батог!
Ми можемо переписати процес, використовуючи нитку, щоб використовувати цей витрачений час:
І так далі. Очевидно, це дещо надуманий приклад, але ви можете бачити, як ця методика може використати час, який інакше було б витрачено на очікування IO.
Зауважте, що нарізання різьбою, як показано вище, може підвищити ефективність лише у процесах, що пов'язані з великим IO. Якщо програма в основному обчислює речі, у нас не буде багато «дірок», над якими ми могли б виконати більше роботи. Крім того, є накладні витрати на кілька інструкцій при переключенні між потоками. Якщо ви запускаєте занадто багато потоків, процесор витратить більшу частину свого часу на перемикання і не дуже багато працює над проблемою. Це називається обмолотом .
Це все добре і добре для одноядерного процесора, але більшість сучасних процесорів мають два і більше ядра. Нитки все ще служать одній і тій же цілі - максимально використовувати CPU, але цього разу ми маємо можливість виконувати дві окремі інструкції одночасно. Це може скоротити час роботи на коефіцієнт, проте багато ядер доступні, оскільки комп'ютер насправді багатозадачний, а не комутаційний контекст.
Маючи декілька ядер, нитки забезпечують метод розщеплення між двома ядрами. Наведене все ще стосується кожного ядра; Програма, яка працює з максимальною ефективністю з двома потоками на одному ядрі, швидше за все, буде працювати з піковою ефективністю з приблизно чотирма потоками на двох ядрах. (Ефективність вимірюється тут мінімальними виконанням інструкцій NOP.)
Проблеми із запуском потоків на декількох ядрах (на відміну від одного ядра), як правило, вирішує апаратне забезпечення. ЦП буде впевнений, що він блокує відповідні місця пам'яті перед читанням / записом до нього. (Я читав, що він використовує для цього спеціальний біт прапора, але це може бути здійснено декількома способами.) Як програмісту з мовами вищого рівня, вам не доведеться більше хвилюватися про два ядра, як ви доведеться з одним.
TL; DR: Нитки можуть розділити роботу, щоб комп'ютер міг асинхронно обробляти кілька завдань. Це дозволяє комп'ютеру працювати з максимальною ефективністю, використовуючи весь доступний час обробки, а не блокуючи, коли процес чекає ресурс.
Що може зробити декілька потоків, коли одна нитка не може?
Нічого.
Простий ескіз:
Однак зауважте, що тут приховано велике припущення: а саме те, що мова, що використовується в одному потоці, є цілковитою Тьюрінгом.
Отже, більш цікавим питанням було б: "Чи може додавання просто багатопотокової мови до мови, що не закінчується Тюрінгом, зробити це Тюрінгом повним?" І я вважаю, відповідь - «Так».
Візьмемо загальнофункціональні мови. [Для тих, хто не знайомий: так само, як функціональне програмування - це програмування з функціями, загальне функціональне програмування - це програмування із загальними функціями.]
Загальні функціональні мови, очевидно, не є повним Тюрінгом: ви не можете записати нескінченний цикл у TFPL (насправді, це майже все визначення "загальний"), але ви можете в машині Тьюрінга, ерго існує принаймні одна програма, яка не може бути записана в TFPL, але може в UTM, тому TFPL менш обчислювально потужні, ніж UTM.
Однак, як тільки ви додасте нарізування до TFPL, ви отримуєте нескінченні петлі: просто виконайте кожну ітерацію циклу в новій нитці. Кожен окремий потік завжди повертає результат, тому це Total, але кожен потік також породжує новий потік, який виконує наступну ітерацію, ad infinitum.
Я думаю, що ця мова була б ціркою Тюрінга.
Принаймні, він відповідає на початкове запитання:
Що може зробити декілька потоків, коли одна нитка не може?
Якщо у вас є мова, яка не може робити нескінченні петлі, то багаторядне введення дозволяє робити нескінченні петлі.
Зверніть увагу, звичайно, що нерестування потоку є побічним ефектом, і тому наша розширена мова є не тільки вже не Total, вона навіть навіть не функціональна.
Теоретично все, що робить багатопотокова програма, можна зробити і з однопотоковою програмою, і лише повільніше.
На практиці різниця швидкостей може бути настільки великою, що немає можливості використовувати однопоточну програму для виконання завдання. Наприклад, якщо у вас щовечора виконується пакетна робота з обробки даних, і для завершення роботи на одному потоці потрібно більше 24 годин, у вас немає іншого вибору, ніж зробити багатопотокове. (На практиці поріг, мабуть, навіть менший: часто такі завдання з оновлення повинні закінчуватися до раннього ранку, перш ніж користувачі знову почнуть використовувати систему. Також від них можуть залежати інші завдання, які також повинні закінчитися в ту ж ніч. Отже, Доступний час виконання може бути низьким, як кілька годин / хвилин.)
Виконання обчислювальної роботи на декількох потоках - це форма розподіленої обробки; ви поширюєте роботу по декількох потоках. Іншим прикладом розподіленої обробки (використання декількох комп'ютерів замість кількох потоків) є заставка SETI: розчавлення стільки даних вимірювань на одному процесорі зайняло б жахливо багато часу, і дослідники вважають за краще бачити результати до виходу на пенсію ;-) Однак вони у вас немає бюджету на оренду суперкомп'ютера настільки довго, тому вони розподіляють роботу на мільйони домашніх ПК, щоб зробити їх дешевим.
Хоча нитки здаються невеликим кроком від послідовного обчислення, насправді вони представляють собою величезний крок. Вони відкидають найважливіші та привабливі властивості послідовних обчислень: зрозумілість, передбачуваність та детермінізм. Нитки, як модель обчислення, дико недетерміновані, і робота програміста стає однією з обрізань цього недетермінізму.
- Проблема з нитками (www.eecs.berkeley.edu/Pubs/TechRpts/2006/EECS-2006-1.pdf).
Хоча є деякі переваги в роботі, які можуть бути використані, використовуючи нитки, в яких ви можете розподілити роботу по декількох ядрах, вони часто отримують велику ціну.
Один з недоліків використання потоків, про які ще не було сказано, - це втрата розділення ресурсів, яку ви отримуєте за допомогою єдиних потокових процесорів. Наприклад, скажімо, ви натрапили на випадок segfault. У деяких випадках можна відновлюватись цим у багатопроцесорному застосуванні, якщо ви просто дозволите дитині, яка винила, померти та відновити нову. Це стосується попереднього форкенду Apache. Коли один екземпляр httpd йде вниз, гіршим випадком є те, що конкретний запит HTTP може бути відхилений для цього процесу, але Apache породить нову дитину і часто запит, якщо його просто обурюють і обслуговують. Кінцевий результат полягає в тому, що Apache в цілому не знімається з несправної нитки.
Іншим врахуванням у цьому сценарії є витоки пам'яті. Є деякі випадки, коли ви можете вишукано обробити обрив потоку (в UNIX можливе відновлення деяких специфічних сигналів - навіть segfault / fpviolation), але навіть у цьому випадку ви, можливо, просочили всю пам'ять, виділену цим потоком (malloc, new тощо). Тож, хоча ваш процес може жити далі, з часом витікає все більше пам’яті з кожною помилкою / відновленням. Знову ж таки, є певною мірою способи мінімізувати подібне використання Apache пулів пам'яті. Але це все ще не захищає від пам’яті, яку, можливо, виділили сторонні лібри, якими могла користуватися нитка.
І, як дехто зазначав, розуміння примітивів для синхронізації - це, мабуть, найскладніше, що дійсно виходить правильно. Ця проблема сама по собі - просто отримання загальної логіки для всього вашого коду - може стати величезним головним болем. Таємничі тупики, як правило, трапляються в найдивніші часи, а іноді навіть до тих пір, поки ваша програма не запущена у виробництво, що ускладнює налагодження все складніше. Додайте до цього той факт, що примітиви синхронізації часто сильно відрізняються від платформи (Windows проти POSIX), а налагодження часто може бути складніше, а також можливість перегонових умов у будь-який час (запуск / ініціалізація, час виконання та вимкнення), програмування з нитками дійсно мало пощади для початківців. І навіть для експертів, все ще мало милосердя лише тому, що знання самої нитки не зводить до мінімуму складність взагалі. Кожен рядок потокового коду іноді, здається, експоненціально ускладнює загальну складність програми, а також збільшує ймовірність появи прихованого глухого кута або дивного стану гонки на будь-який час. Також може бути дуже важко написати тестові випадки, щоб розірвати ці речі.
Ось чому деякі проекти, такі як Apache та PostgreSQL, здебільшого базуються на процесах. PostgreSQL запускає кожен потік потоку в окремому процесі. Звичайно, це все ще не полегшує проблеми синхронізації та умов перегонів, але це додає досить небагато захисту та певним чином спрощує справи.
Кілька процесів, кожен з яких виконує один потік виконання, можуть бути значно кращими, ніж кілька потоків, що працюють в одному процесі. І з появою більшої частини нового однорангового коду, як AMQP (RabbitMQ, Qpid та ін.) Та ZeroMQ, набагато простіше розділити потоки по різних технологічних просторах і навіть машинах та мережах, значно спрощуючи речі. Але все-таки це не срібна куля. Складно вирішити проблеми. Ви просто переміщаєте деякі змінні з процесного простору в мережу.
Суть полягає в тому, що рішення про введення в область ниток не є легким. Як тільки ви ступаєте на цю територію, майже миттєво все стає складнішим, і у ваше життя вступають цілі нові породи проблем. Це може бути весело і круто, але це як атомна енергетика - коли справи йдуть не так, вони можуть йти погано і швидко. Я пам’ятаю, як багато років тому проходили заняття з критичної критики, і вони показали фотографії деяких вчених з Лос-Аламосу, які грали з плутонієм у лабораторіях ще у часи Другої світової війни. Багато хто вживав мало заходів щодо запобігання події, якщо їх не було, і за мить ока - одним яскравим, безболісним спалахом, для них все закінчиться. Через кілька днів вони були мертві. Пізніше Річард Фейнман назвав це " лоскотом дракона за хвіст"Це те, як може виглядати гра з нитками (принаймні, для мене в будь-якому випадку). Спочатку це здається досить нешкідливим, а до моменту, коли ви покусали, чухаєте голову про те, як швидко погіршилися речі. Але принаймні нитки виграли я тебе не вб'ю.
По-перше, однопотокове додаток ніколи не скористається багатоядерним процесором або гіпер-потоком. Але навіть на одній ядрі однопоточний процесор, що робить багатопотокові, має переваги.
Розгляньте альтернативу і чи це робить вас щасливим. Припустимо, у вас є кілька завдань, які потрібно виконувати одночасно. Наприклад, вам потрібно продовжувати спілкуватися з двома різними системами. Як це зробити без багатопотокової передачі? Ви, ймовірно, створили власний планувальник і дозволили йому викликати різні завдання, які потрібно виконати. Це означає, що вам потрібно розділити свої завдання на частини. Ймовірно, вам потрібно відповідати деяким обмеженням у режимі реального часу, ви повинні переконатися, що ваші деталі не займають занадто багато часу. Інакше таймер закінчиться в інших завданнях. Це ускладнює розбиття завдання. Чим більше завдань вам потрібно буде керувати собою, тим більше розщеплення вам потрібно зробити і тим складнішим стане ваш планувальник для задоволення всіх обмежень.
Якщо у вас є кілька ниток, життя може стати простішим. Попереджувальний планувальник може зупинити потік у будь-який час, зберегти його стан та повторно (запустити) інший. Він перезапуститься, коли ваша нитка отримає свою чергу. Переваги: складність написання планувальника вже зроблена для вас, і вам не доведеться розділяти свої завдання. Також планувальник здатний керувати процесами / потоками, про які ви самі навіть не знаєте. А також, коли потоці нічого не потрібно робити (він чекає якоїсь події), він не займе циклів процесора. Це не так просто здійснити під час створення однопотокового планувальника. (укладати щось спати не так складно, але як воно прокидається?)
Мінусом багатопотокової розробки є те, що вам потрібно зрозуміти проблеми одночасності, стратегії блокування тощо. Розробка безпомилкового багатопотокового коду може бути досить важким. А налагодження може бути ще складніше.
чи існує щось таке, що може бути досягнуто лише за допомогою декількох потоків?
Так. Ви не можете запустити код на декількох процесорах або процесорних ядрах одним потоком.
Без декількох процесорів / ядер потоки все ще можуть спростити код, який концептуально працює паралельно, наприклад, обробка клієнта на сервері - але ви можете зробити те ж саме без потоків.
Нитки стосуються не лише швидкості, але й одночасності.
Якщо у вас немає пакетної програми, як запропонував @Peter, а натомість інструментарій GUI типу WPF, як ви могли взаємодіяти з користувачами та діловою логікою лише одним потоком?
Припустимо, ви будуєте веб-сервер. Як би ви одночасно обслуговували більше одного користувача лише одним потоком (припустимо, що немає інших процесів)?
Існує багато сценаріїв, коли лише одного простого потоку недостатньо. Тому останні досягнення, такі як процесор Intel MIC з більш ніж 50 ядрами і сотнями потоків, відбуваються.
Так, паралельне та паралельне програмування важко. Але необхідно.
Multi-Threading дозволяє інтерфейсу GUI все ще реагувати під час тривалих операцій обробки. Без багатопотокової передачі користувач застряг би переглядати заблоковану форму під час тривалого процесу.
Багатопотоковий код може зупинити логіку програми та отримувати доступ до застарілих даних таким чином, що не можуть поодинокі потоки.
Нитки можуть взяти незрозумілу помилку від чогось, від середнього програміста, можна очікувати налагодження та переміщення її у царину, де розповідаються історії про удачу, необхідну для того, щоб зловити ту саму помилку, коли штани опускаються, коли випадково програміст дивився лише на правильний момент.
додатки, що займаються блокуванням IO, які також повинні відповідати іншим входам (GUI або інші з'єднання), не можуть бути однократними
додавання методів перевірки у вкладці вводу-виводу, щоб побачити, скільки можна прочитати без блокування, може допомогти цьому, але не багато бібліотек надають повних гарантій щодо цього
Дуже багато хороших відповідей, але я не впевнений, що будь-яка фраза є цілком такою, якою я хотіла - можливо, це пропонує інший спосіб поглянути на це:
Нитки - це просто спрощення програмування, як об’єкти чи дійові особи, або для циклів (так, все, що ви реалізуєте за допомогою циклів, які можна реалізувати за допомогою if / goto).
Без потоків ви просто реалізуєте двигун стану. Мені довелося це робити багато разів (Перший раз, коли я це зробив, я ніколи про це не чув - просто зробив велику заяву про перемикання, керовану змінною "State"). Державні машини все ще досить поширені, але можуть дратувати. З нитками відходить величезний шматок котла.
Вони також роблять полегшенням мови, щоб порушити її виконання на багатопроцесорних блоках (так я вважаю, актори).
Java надає "зелені" потоки в системах, де ОС не забезпечує жодної підтримки потоків. У цьому випадку простіше помітити, що вони явно не що інше, як абстракція програмування.
В ОС використовується концепція відсікання часу, де кожен потік отримує час запускати, а потім отримує викуп. Такий підхід може замінити нитки, як зараз, але писати власні планувальники у кожну програму було б надмірно. Крім того, вам доведеться працювати з пристроями вводу / виводу тощо. І знадобиться деяка підтримка з боку апаратних засобів, щоб ви могли запускати переривання для запуску планувальника. В основному ви б писали нову ОС кожен раз.
Взагалі нитка може покращити продуктивність у випадках, коли нитки чекають вводу / виводу або сплять. Це також дозволяє робити інтерфейси, які відповідають і дозволяють зупиняти процеси, виконуючи довгі завдання. А також, потоки покращують речі на справжніх багатоядерних процесорах.
По-перше, нитки можуть робити дві або більше речей одночасно (якщо у вас є більше ядра). Хоча ви також можете це зробити за допомогою декількох процесів, деякі завдання просто не дуже добре розподіляються по декількох процесах.
Крім того, у деяких завданнях є пробіли, яких ви не можете легко уникнути. Наприклад, важко читати дані з файлу на диску, а також змусити ваш процес робити щось інше одночасно. Якщо для вашого завдання обов'язково потрібно багато читання даних з диска, ваш процес витратить багато часу на очікування диска незалежно від того, що ви робите.
По-друге, потоки дозволяють вам уникнути необхідності оптимізації великої кількості вашого коду, що не є критичним для продуктивності. Якщо у вас є лише одна нитка, кожен фрагмент коду є критичним для продуктивності. Якщо він блокується, ви потоплені - жодні завдання, які були б виконані цим процесом, не можуть досягти прогресу вперед. З потоками блок впливатиме лише на те, що потоки та інші потоки можуть узгоджуватися та працювати над завданнями, які необхідно виконати цим процесом.
Хорошим прикладом є нечасто виконаний код обробки помилок. Скажімо, що завдання стикається з дуже рідкісною помилкою, і код, який обробляє цю помилку, повинен переходити в пам'ять. Якщо диск зайнятий, а процес має лише один потік, не можна досягти прогресу вперед, поки код для обробки цієї помилки не може бути завантажений в пам'ять. Це може викликати бурхливу реакцію.
Інший приклад - якщо вам дуже рідко доведеться робити пошук у базі даних. Якщо ви будете чекати, коли база даних відповість, ваш код спричинить величезну затримку. Але ви не хочете піти на проблему з тим, щоб зробити весь цей код асинхронним, оскільки це так рідко, що вам потрібно зробити ці пошуки. Маючи нитку для виконання цієї роботи, ви отримуєте найкраще з обох світів. Нитка для цієї роботи робить її неефективною, як це має бути.