Нитки проти процесів у Linux


253

Нещодавно я чув, як мало хто говорить, що в Linux майже завжди краще використовувати процеси замість потоків, оскільки Linux дуже ефективний в обробці процесів і тому, що існує багато таких проблем (як блокування), пов'язаних з потоками. Однак я підозріло, тому що, здається, нитки можуть дати досить великий приріст у деяких ситуаціях.

Отже, моє запитання полягає в тому, що, якщо стикатися з ситуацією, коли нитки та процеси можуть досить добре впоратися, я повинен використовувати процеси чи нитки? Наприклад, якщо я писав веб-сервер, чи слід використовувати процеси або потоки (або комбінацію)?


Чи є різниця з Linux 2.4?
mouviciel

3
Різниця між процесами та потоками під Linux 2.4 полягає в тому, що потоки поділяють більше частин свого стану (адресний простір, обробку файлів тощо), ніж процеси, які зазвичай не мають. NPTL під Linux 2.6 робить це трохи зрозуміліше, надаючи їм "потокові групи", схожі на "процеси" в win32 та Solaris.
MarkR

6
Одночасне програмування складно. Якщо вам не потрібна дуже висока продуктивність, найважливішим аспектом вашого компромісу часто буде складність налагодження . Процеси набагато простіше вирішити в цьому відношенні, оскільки все спілкування є явним (легко перевірити, увійти в систему тощо). Навпаки, спільна пам'ять потоків створює газильйони місць, де одна нитка може помилково впливати на іншу.
Lutz Prechelt

1
@LutzPrechelt - Одночасне програмування може бути багатопоточним, а також багатопроцесорним. Я не бачу, чому ви припускаєте, що одночасне програмування є багатопоточним. Це може бути через певні мовні обмеження, але загалом це може бути і те, і інше.
iankit

2
Я посилаюся, що Лутц просто зазначив, що одночасне програмування важко, незалежно від того, який з них вибирається - процес чи потоки, - але паралельне програмування з використанням процесів полегшує налагодження у багатьох випадках.
користувач2692263

Відповіді:


322

Linux використовує модель різьблення 1-1, при цьому (до ядра) немає різниці між процесами та потоками - все це просто виконується завдання. *

У Linux системний виклик cloneклонує завдання із налаштованим рівнем спільного доступу, серед яких:

  • CLONE_FILES: спільний доступ до тієї ж таблиці дескрипторів файлів (замість створення копії)
  • CLONE_PARENT: не встановлюйте стосунки батько-дитина між новим завданням і старим (інакше дитина getppid()= батько getpid())
  • CLONE_VM: спільний обсяг пам’яті (замість створення копії COW )

fork()дзвінки, що мають clone(найменший обмін, )і pthread_create()дзвінки, що надають clone(більшість ). **

forking коштує трохи більше, ніж pthread_createing через копіювання таблиць та створення відображень COW для пам'яті, але розробники ядра Linux намагалися (і досягли успіху) мінімізувати ці витрати.

Перемикання між завданнями, якщо вони мають один і той же простір пам'яті та різні таблиці, буде трохи дешевше, ніж якщо вони не поділяться, тому що дані вже можуть бути завантажені в кеш. Однак завдання комутації все ще дуже швидко, навіть якщо нічого не ділиться - це щось інше, що розробники Linux ядра намагаються забезпечити (і досягти успіху).

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


* Спрощена. CLONE_THREADспричиняє поділ сигналів для спільного використання (що потрібно CLONE_SIGHAND, яке розділяє таблицю обробки сигналів).

** Спрощений. Існують SYS_forkі SYS_clonesscall, але в ядрі є sys_forkі sys_cloneобидва дуже тонкі обгортки навколо тієї ж do_forkфункції, яка сама по собі є тонкою обгорткою навколо copy_process. Так, умови process, threadі taskвикористовуються як взаємозамінні , а в ядрі Linux ...


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

26
@Saurabh Не дуже. Ви можете легко socket, bind, listen, fork, а потім кілька процесів acceptз'єднання на одному сокеті. Процес може перестати приймати, якщо він зайнятий, і ядро ​​буде перенаправляти вхідні з'єднання до іншого процесу (якщо ніхто не слухає, ядро ​​буде стояти в черзі чи випадати, залежно від listenвідставання). Ви не маєте набагато більшого контролю над розподілом роботи, ніж це, але зазвичай це досить добре!
ефеміент

2
@Bloodcount Усі процеси / потоки в Linux створюються тим самим механізмом, який клонує існуючий процес / потік. Позначені прапори, щоб clone()визначити, якими ресурсами поділяються. Завдання може також використовувати unshare()ресурси в будь-який наступний момент часу.
ефемієнт

4
@KarthikBalaguru У самому ядрі є task_structкожне завдання. Це часто називають "процесом" у всьому коді ядра, але він відповідає кожному потоку, який можна виконати. Немає process_struct; якщо купа task_structs пов'язана разом зі своїм thread_groupсписком, то вони є тим самим "процесом" в просторі користувачів. Існує трохи спеціального поводження з "ниткою", наприклад, всі нитки братів і сестер зупиняються на fork та exec, і відображається лише "головна" нитка ls /proc. Кожен потік доступний через, /proc/pidхоча він перелічений /procчи ні.
ефемія

5
@KarthikBalaguru Ядро підтримує континуум поведінки між потоками та процесами; наприклад, clone(CLONE_THREAD | CLONE_VM | CLONE_SIGHAND))дасть вам новий "потік", який не ділиться робочою директорією, файлами чи блоками, тоді як clone(CLONE_FILES | CLONE_FS | CLONE_IO)дав би вам "процес". Базова система створює завдання шляхом клонування; fork()і pthread_create()це лише бібліотечні функції, які викликають по- clone()різному (як я писав у цій відповіді).
ефемія

60

Linux (і справді Unix) дає вам третій варіант.

Варіант 1 - процеси

Створіть автономний виконуваний файл, який обробляє деяку частину (або всі частини) вашої програми, і викликайте її окремо для кожного процесу, наприклад, програма запускає копії себе, щоб делегувати завдання.

Варіант 2 - нитки

Створіть автономний виконуваний файл, який запускається однією ниткою, і створіть додаткові потоки для виконання деяких завдань

Варіант 3 - виделка

Доступно лише під Linux / Unix, це трохи інакше. Процес роздвоєння насправді є власним процесом із власним адресним простором - нічого, що може зробити дитина (зазвичай), щоб вплинути на адресний простір свого батька чи побратимів (на відміну від потоку) - так ви отримуєте додаткову надійність.

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

Розглянемо програму веб-сервера, яка складається з двох етапів:

  1. Прочитайте дані конфігурації та часу виконання
  2. Подавайте запити на сторінку

Якщо ви використовували потоки, крок 1 буде виконано один раз, а крок 2 - у кількох потоках. Якщо ви використовували "традиційні" процеси, кроки 1 і 2 потрібно буде повторити для кожного процесу, а пам'ять для зберігання даних конфігурації та часу виконання. Якщо ви використовували fork (), ви можете зробити крок 1 один раз, а потім fork (), залишивши дані часу виконання та конфігурацію в пам’яті, недоторканими, не скопійованими.

Тож дійсно є три варіанти.


7
@Qwertie розгортання не так круто, воно розбиває безліч бібліотек тонко (якщо ви використовуєте їх у батьківському процесі). Це створює несподіване поведінку, яке бентежить навіть досвідчених програмістів.
МаркР

2
@MarkR Ви могли б навести кілька прикладів або посилання, як роздрібнювати бібліотеку розривів і створювати несподівану поведінку?
Ehtesh Choudhury

18
Якщо процес розщеплюється з відкритим з'єднанням mysql, трапляються погані речі, оскільки сокет розділяється між двома процесами. Навіть якщо лише один процес використовує з'єднання, інший зупиняє його закриття.
МаркР

1
Системний виклик fork () визначається POSIX (це означає, що він доступний у будь-яких системах Unix), якщо ви використовували базовий API Linux, який є системним викликом clone (), то у вас в Linux навіть більше варіантів, ніж лише три .
Лежи Райан

2
@MarkR Розміщення розетки відбувається за дизайном. Крім того, будь-який з процесів може закрити сокет за допомогою linux.die.net/man/2/shutdown перед тим, як викликати close () на сокет.
Лелантран

53

Це залежить від безлічі факторів. Процеси мають більшу вагу, ніж потоки, і мають більшу вартість запуску та відключення. Міжпроцесовий зв'язок (IPC) також складніше і повільніше, ніж міжпотокове спілкування.

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

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


9
Відповідь Адама добре послужила б інструктажем керівництва. Для більш детальної інформації, MarkR та ефеміент дають хороші пояснення. Дуже детальне пояснення з прикладами можна знайти на cs.cf.ac.uk/Dave/C/node29.html, але воно, схоже, є частинним датуванням .
CyberFonic

2
CyberFonic стосується Windows. Як говорить ephemient, в Linux процеси не важчі. А в Linux усі механізми для зв'язку між потоками (futex, спільна пам'ять, труби, IPC) також доступні для процесів і працюють з однаковою швидкістю.
Рассел Стюарт

IPC важче використовувати, але що робити, якщо хтось використовує "спільну пам'ять"?
abhiarora

11

Інші обговорили міркування.

Можливо, важлива відмінність полягає в тому, що в Windows процеси важкі і дорогі порівняно з потоками, а в Linux різниця набагато менша, тому рівняння врівноважується в іншій точці.


9

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

У сучасному Linux (2.6.x) велика різниця у продуктивності між контекстним перемиканням процесу порівняно з потоком (лише потоки MMU є додатковими для потоку). Виникає проблема із спільним адресним простором, а це означає, що несправний вказівник у потоці може пошкодити пам'ять батьківського процесу або інший потік у тому ж адресному просторі.

Процес захищений MMU, тому несправний покажчик просто спричинить сигнал 11 і не пошкодить.

Я б загалом користувався процесами (не багато перемикання контексту в режимі Linux, але захист пам’яті завдяки MMU), але pthreads, якщо мені потрібен клас планувальника в реальному часі, який є різною чашкою чаю разом.

Чому, на вашу думку, нитки мають такий великий приріст продуктивності в Linux? Чи є у вас якісь дані для цього чи це просто міф?


1
Так, у мене є деякі дані. Я провів тест, який створює 100 000 процесів, і тест, який створює 100 000 ниток. Версія потоку працювала приблизно в 9 разів швидше (17,38 секунди для процесів, 1,93 для потоків). Тепер це лише тестовий час створення, але для короткочасних завдань час створення може бути ключовим.
користувач17918

4
@ user17918 - Чи можна поділитися кодом, який ви використовували для обчислення вищезгаданих термінів ..
codingfreak

одна велика інша, з процесами ядро ​​створює таблицю сторінок для кожного процесу, а тести використовують лише одні таблиці таблиць, тому я думаю, що це нормально, потоки швидші, ніж процеси
c4f4t0r

Ще один простий спосіб поглянути на це - TCB - це набагато менше, ніж PCB, і тому очевидно, що контекстний перемикач процесу, який передбачає PCB, займе трохи більше часу, ніж перемикання потоків.
Karthik Balaguru

5

Наскільки щільно поєднані ваші завдання?

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


4

Щоб ще більше ускладнити питання, існує таке поняття, як локальне зберігання потоків і спільна пам'ять Unix.

Місцеве сховище потоків дозволяє кожному потоку мати окремий екземпляр глобальних об'єктів. Єдиний раз, коли я використовував це, коли будував середовище емуляції на Linux / Windows, для коду програми, який працював у RTOS. У RTOS кожне завдання було процесом із власним адресним простором, у середовищі емуляції кожне завдання являло собою потік (із спільним адресним простором). Використовуючи TLS для таких речей, як одиночні, ми мали змогу мати окремий екземпляр для кожного потоку, як і в «реальному» середовищі RTOS.

Спільна пам'ять може (очевидно) принести вам переваги продуктивності наявності декількох процесів доступу до однієї пам’яті, але ціною / ризиком належної синхронізації процесів. Один із способів зробити це - створити один процес створення структури даних у спільній пам’яті, а потім надіслати ручку до цієї структури за допомогою традиційного міжпроцесового зв’язку (як-от названа труба).


1
Я використовував локальне зберігання потоків для деякого збору статистики, востаннє, коли я писав програму з потоковими мережами: кожен потік записував у власні лічильники, не потрібні блокування, і лише при обміні повідомленнями кожен потік поєднує свою статистику з загальними підсумками. Але так, TLS не дуже часто використовується або необхідний. Спільна пам'ять, з іншого боку ... окрім ефективної передачі даних, ви також можете ділитися семафорами POSIX між процесами, розміщуючи їх у спільній пам'яті. Це досить дивовижно.
ефемія

4

У моїй недавній роботі з LINUX одна річ, яку слід пам’ятати - це бібліотеки. Якщо ви використовуєте потоки, переконайтесь, що будь-які бібліотеки, які ви можете використовувати в різних потоках, є безпечними для потоків. Це спалило мене кілька разів. Зокрема, libxml2 не є безпечним для потоків із коробки. Він може бути складений за допомогою безпечного потоку, але це не те, що ви отримуєте при встановленні можливостей.


3

Я мав би погодитися з тим, що ви чули. Коли ми орієнтуємо наш кластер ( xhplі такий), ми завжди отримуємо значно кращі показники роботи над потоками.</anecdote>


3

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

На прикладі реального світу веб-сервера apache 1.3 використовувався лише для підтримки декількох процесів, але в 2.0 вони додали абстракцію, щоб ви могли перемикатися між ними. Коментарі, схоже, згодні з тим, що процеси є більш надійними, але потоки можуть дати трохи кращу продуктивність (за винятком Windows, де продуктивність процесів затримується, і ви хочете використовувати лише теми).


2

У більшості випадків я віддаю перевагу процесам над потоками. потоки можуть бути корисні, коли у вас є відносно менша задача (накладні витрати >>, час зайнятий кожним розділеним блоком завдань) і між ними є необхідність обміну пам'яттю. Подумайте великий масив. Також (offtopic) зауважте, що якщо використання вашого процесора становить 100 відсотків або близьке до нього, від багатопотокового чи оброблення не буде користі. (насправді це погіршиться)


Що ти не маєш на увазі? Як щодо виконання великих обчислень у потоці GUI? Переміщення їх до паралельної нитки буде набагато краще з точки зору користувальницької роботи, незалежно від того, як завантажується процесор.
Олегст

2

Нитки -> Нитки розділяють простір пам’яті, це абстракція процесора, вона легка. Процеси -> Процеси мають свій простір пам'яті, це абстракція комп’ютера. Щоб паралелізувати завдання, потрібно абстрагувати процесор. Однак переваги використання процесу над потоком - це безпека, стабільність, в той час як потік використовує менше пам'яті, ніж процес, і пропонує меншу затримку. Прикладом з точки зору Інтернету можуть бути хром та фаєрфокс. У разі Chrome кожна вкладка - це новий процес, отже, використання хромової пам'яті вище, ніж Firefox, тоді як забезпечена безпека та стабільність кращі, ніж у Firefox. Безпека, що надається chrome, краща, оскільки кожна вкладка є новою технологією, інша вкладка не може проскочити в пам'ять даного процесу.


2

Я думаю, що всі зробили чудову роботу, відповідаючи на ваше запитання. Я просто додаю більше інформації про потік проти процесу в Linux, щоб уточнити та узагальнити деякі попередні відповіді в контексті ядра. Отже, моя відповідь стосується конкретного коду ядра в Linux. Згідно з документацією Linux Kernel, немає чіткого розрізнення між потоком та процесом, за винятком того, що потоки використовують спільний віртуальний адресний простір на відміну від процесу. Також зауважте, що ядро ​​Linux використовує термін "завдання" для позначення процесу та потоку в цілому.

"Немає внутрішніх структур, що реалізують процеси або потоки, натомість існує структура task_struct, яка описує абстрактний блок планування, який називається завданням"

Крім того, згідно з Лінусом Торвальдсом, ви НЕ повинні думати про процес проти потоку взагалі, і тому, що це занадто обмежує і єдиною різницею є COE або контекст виконання в термінах "відокремити адресний простір від батьківського" або спільний адресний простір. Насправді він використовує приклад веб-сервера, щоб зробити свою думку тут (що настійно рекомендую прочитати).

Повна кредитна документація на ядро ​​Linux


-3

Якщо вам потрібно поділитися ресурсами, ви дійсно повинні використовувати теми.

Також врахуйте той факт, що контекстні комутатори між потоками значно дешевші, ніж контекстні комутатори між процесами.

Я не бачу причин явно працювати з окремими процесами, якщо у вас немає вагомих причин для цього (безпека, перевірені показники ефективності тощо).


3
У мене є редактор, який потрібно редагувати, але я не зовсім згоден. Контекстні комутатори між процесами в Linux майже такі ж дешеві, як і контекстні комутації між потоками.
ефемія
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.