Скільки ниток занадто багато?


312

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

Моє запитання: що таке хороша точка відсічення для таких потоків вводу / виводу? Я знаю, що це була б просто приблизна оцінка, але ми говоримо сотні? Тисячі?

Як би я міг розібратися, яким буде це обрізання?


Редагувати:

Дякую всім за ваші відповіді, схоже, мені просто доведеться перевірити це, щоб дізнатись мій перерахунок потоку потоку. Питання все-таки: як я знаю, що я потрапив у цю стелю? Що саме слід виміряти?


1
@ryeguy: Вся суть тут у тому, що ви не повинні встановлювати жодного максимуму в нитковій пулі, якщо немає проблем з продуктивністю для початку. Більшість порад щодо обмеження нитки нитки до ~ 100 ниток є смішними, більшість пулів ниток мають / way / більше потоків, ніж це, і ніколи не виникають проблеми.
ГЕОЧЕТ

Райже, див. додаток до моєї відповіді нижче про те, що міряти.
paxdiablo

Не забувайте, що Python за своєю природою насправді не є багатопоточним. У будь-який момент часу виконується один байт-код коду. Це тому, що в Python використовується Global Interpreter Lock.
ASK

1
@ Джей Д: Я б сказала, що момент, коли ти вдарився до стелі, - це коли твоя продуктивність починає падати.
ніндзя

6
@GEOCHET "Вся суть у тому, що ви не повинні встановлювати жодного максимуму в нитці" Ummm ... скажіть що? Басейни з фіксованими розмірами мають переваги витонченої деградації та масштабованості. Наприклад, у мережевому налаштуванні, якщо ви нерестуєте нові потоки на основі з'єднань з клієнтами, без фіксованого розміру пулу ви створюєте дуже реальну небезпеку навчання ( важкий шлях ) лише скільки ниток може обробляти ваш сервер та кожного підключеного клієнта постраждає. Пул фіксованого розміру діє як клапан труби, забороняючи ваш сервер намагатися відкусити більше, ніж він може жувати.
b1nary.atr0phy

Відповіді:


206

Деякі люди скажуть, що двох ниток занадто багато - я не зовсім у тому таборі :-)

Ось моя порада: міряйте, не здогадуйтесь. Одне із пропозицій - зробити його конфігуруваним і спочатку встановити його на 100, а потім випустити програмне забезпечення на диво та стежити за тим, що відбувається.

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

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


Для уточнення та розробки:

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

Я написав код об'єднання з'єднань для потоків і баз даних, і вони мають такі функції (які, на мою думку, є важливими для продуктивності):

  • мінімальна кількість активних потоків.
  • максимальна кількість ниток.
  • вимкнення тем, які не використовувались деякий час.

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

Вам потрібно врівноважити використання ресурсів, що мають невикористані потоки (A), проти використання ресурсу не має достатньої кількості потоків для виконання роботи (B).

(A) - це звичайно використання пам'яті (стеки тощо), оскільки нитка, яка не працює, не буде використовувати більшу частину процесора. (B), як правило, буде затримка в обробці запитів, коли вони надходять, як вам потрібно дочекатися, коли нитка стане доступною.

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

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

Тоді кількість потоків у вас має залежати від історичного використання. Мінімальний, який у вас повинен бути біг, - це мінімальна кількість, яку ви коли-небудь працювали + A%, з абсолютним мінімумом (наприклад, і зробіть його налаштованим так само, як A) 5.

Максимальна кількість потоків має бути вашим історичним максимумом + B%.

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


У відповідь на "що саме я повинен вимірювати?" питання:

Те, що вам слід конкретно виміряти, - це максимальна кількість потоків при одночасному використанні (наприклад, очікування повернення з виклику БД) під навантаженням. Потім додати коефіцієнт запасу міцності на 10% , наприклад (виділено, так як інші плакати , здається, прийняти мої приклади як основні рекомендації).

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


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

@Andrew, створення ниток вимагає часу, і ви можете визначити оптимальне число на основі історичних даних [+ N%] (отже, міру, не здогадуйтесь). Крім того, більше потоків викликає суперечки з ресурсами лише тоді, коли вони роблять роботу, не чекаючи сигналу / семафору.
paxdiablo

Де ці дані про "створення ниток" викликають проблеми з продуктивністю при використанні пулу потоків? Хороший пул потоків не створював би і не руйнував нитки між завданнями.
ГЕОЧЕТ

@Pax Якщо всі ваші потоки чекають на одних і тих же семафорах для запуску запитів БД, то це саме визначення суперечки. Також не вірно сказати, що нитки не коштують нічого, якщо вони чекають на семафор.
Ендрю Грант

1
@Andrew, я не можу зрозуміти, чому б ти семафорував блокування запитів БД, будь-який пристойний БД дозволить одночасний доступ, багато відповідей очікують на відповіді. І потоки не повинні витрачати жодного часу на виконання, поки семафор заблокований, вони повинні сидіти в заблокованій черзі до виходу семафору.
paxdiablo

36

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

  1. Розмір стека нитки: в Linux розмір стека потоку за замовчуванням становить 8 МБ (ви можете використовувати ulimit -a, щоб дізнатися це).
  2. Макс віртуальної пам'яті, яку підтримує даний варіант ОС. Linux Kernel 2.4 підтримує адресний простір пам'яті в 2 Гб. з ядром 2.6, я трохи більший (3 Гб)
  3. [1] показано обчислення максимальної кількості потоків на заданий підтримуваний Макс. За 2,4 виходить приблизно 255 ниток. для 2,6 число трохи більше.
  4. Який планувальник ядра kindda у вас є. Порівнюючи планувальник ядер Linux 2.4 з 2.6, пізніше дає вам планування O (1), не залежно від кількості завдань, що існують в системі, а перша - більше O (n). Тож також можливості SMP графіку ядра також грають хорошу роль у максимальній кількості стійких потоків у системі.

Тепер ви можете настроїти розмір стека, щоб включити більше потоків, але тоді вам доведеться враховувати накладні витрати управління потоками (створення / знищення та планування). Ви можете примусити Affinity CPU до певного процесу, а також до даного потоку, щоб прив’язати їх до конкретних процесорів, щоб уникнути міграції потоків між центральними процесорами та уникнути проблем з готівкою.

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

Я впевнений, що планувальник ядер Windows також робить щось подібне для вирішення проблем із надмірним використанням ресурсів

[1] http://adywicaksono.wordpress.com/2007/07/10/i-can-not-create-more-than-255-threads-on-linux-what-is-the-solutions/


17

Якщо ваші потоки виконують будь-які ресурсомісткі роботи (процесор / диск), ви рідко побачите переваги, що перевищують один-два, і занадто багато вбиватимуть продуктивність дуже швидко.

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

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

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


Так, і його слід використовувати разом з чергою або пулом запитів.
Ендрю Грант

2
@Andrew: Чому? Він повинен додавати завдання до пулу потоків кожного разу, коли отримує запит. До пулу потоків слід виділити потік для завдання, коли є такий.
ГЕОЧЕТ

То що робити, коли у вас надходять сотні запитів і знаходяться поза потоками? Створити більше? Блок? Повернути помилку? Розмістіть свої запити в пулі, який може бути настільки великим, скільки потрібно, а потім подайте ці запити в чергу до пулу потоків, коли потоки стануть вільними.
Ендрю Грант

"створено ряд потоків для виконання ряду завдань, які зазвичай організовані в черзі. Зазвичай, завдань набагато більше, ніж потоків. Як тільки нитка виконає своє завдання, вона запитає наступне завдання з черги. поки всі завдання не будуть виконані ".
ГЕОЧЕТ

@Andrew: Я не впевнений, який пул потоків python використовує ОП, але якщо ви хочете реальний приклад цього функціоналу, я описую: msdn.microsoft.com/en-us/library/…
GEOCHET

10

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

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


4
Прочитавши це, я спробував запустити сито завдань Ератостена на три нитки. Звичайно, це було насправді на 50% повільніше, ніж виконання одних і тих же завдань в одному потоці. Дякую за голови вгору Я запускав Eclipse Pydev на віртуальній машині, якій було виділено два процесора. Далі я спробую сценарій, який включає деякі дзвінки до бази даних.
Дон Кіркбі

3
Є два (принаймні) типи завдань: пов'язані з процесором (наприклад, обробка зображення) та зв'язані введення / виведення (наприклад, завантаження з мережі). Очевидно, що "проблема" GIL не надто вплине на завдання, пов'язані з входом / виводом. Якщо ваші завдання пов'язані з процесором, вам слід розглянути можливість багатопроцесорної обробки замість багатопотокової читання.
iutinvg

1
так, нитка python покращиться, якщо у вас багато мережі io.I поміняю її на нитку і отримав 10 * швидше, ніж звичайний код ...
tyan

8

Як справедливо сказав Пакс, міряй, не здогадуйся . Це те, що я зробив для DNSwitness, і результати дивували : ідеальна кількість потоків була набагато вищою, ніж я думав, щось на зразок 15000 ниток, щоб отримати найшвидші результати.

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

Завершити заходи (лише французькою мовою) у Combien de fils d'exécution? .


1
15 000? Це на 0 раз вище, ніж я б і очікував. І все-таки, якщо це те, що ти маєш, то це і є, я не можу з цим посперечатися.
paxdiablo

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

18
Я думаю, що якщо у вас є 15000 потоків, які блокуються на зовнішньому вході / виводі, то кращим рішенням буде значно менше ниток, але з асинхронною моделлю. Я говорю з досвіду тут.
Стів

5

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

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

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


1
Чи можете ви згадати деякі цифри, які ви бачили для підрахунку ниток? Було б корисно просто зрозуміти це. Дякую.
ковач

3

Відповідь "великого заліза" - це, як правило, один потік на обмежений ресурс - процесор (пов'язаний з процесором), кронштейн (пов'язаний з входом / виводом) тощо, але це працює лише в тому випадку, якщо ви можете направити роботу на правильний потік ресурсу до отримати доступ до них

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

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

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

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


2

Варто врахувати, скільки ядер існує на машині, яка виконує код. Це являє собою жорсткий ліміт щодо кількості потоків у будь-який момент. Однак якщо, як і у вашому випадку, очікується, що потоки будуть часто чекати, коли база даних виконає запит, ви, ймовірно, захочете налаштувати свої потоки на основі кількості одночасних запитів, які може обробити база даних.


2
гм, ні. Вся суть потоків (до того, як багатоядерні та численні процесори стали переважаючими) полягає в тому, щоб мати можливість імітувати наявність декількох процесорів на машині, яка має лише один. Ось так ви отримуєте чуйні користувальницькі інтерфейси - головну нитку та допоміжні потоки.
mmr

1
@mmr: Гм ні. Ідея потоків полягає в тому, щоб дозволити блокувати введення-виведення та інші завдання.
ГЕОЧЕТ

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

1
У будь-якому випадку - у вас є GIL в Python, який робить нитки лише теоретично паралельними. Не більше ніж 1 потік може працювати одночасно, тому важлива лише оперативність реагування та блокування.
Абган

2
+1 Для того, щоб зрозуміти, як працюють комп'ютери. @mmr: Вам потрібно зрозуміти, що різниця між, як видається, є декількома процесорами і має декілька процесорів. @Rich B: Пул потоків - лише один із багатьох способів обробки колекції потоків. Він хороший, але точно не єдиний.
скорботи

2

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


1
Для Python це особливо вірно, оскільки багато процесів можуть працювати паралельно, тоді як декілька потоків - ні. Однак вартість досить висока. Вам потрібно кожного разу запускати новий інтерпретатор Python та підключатись до БД з кожним процесом (або використовувати перенаправлення деяких труб, але це також коштує).
Абган

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

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

@mmr: Розглядаючи питання про / thread / пули, так, я думаю, що люди повинні очікувати відповіді про теми.
ГЕОЧЕТ

Створення процесу може бути здійснено один раз при запуску (тобто пул процесів замість пулу потоків). Амортизований протягом тривалості заявки, це може бути невеликим. Вони не можуть легко ділитися інформацією, але це НЕ купує їм можливість працювати на декількох процесорах, тому ця відповідь корисна. +1.
paxdiablo

1

ryeguy, я зараз розробляю подібну програму, і номер моїх потоків встановлено на 15. На жаль, якщо я збільшую її на 20, вона вийде з ладу. Отже, так, я думаю, що найкращий спосіб впоратися з цим - це виміряти, чи дозволяє ваша поточна конфігурація більше чи менше, ніж число X потоків.


5
Додавання до кількості потоків не повинно випадково завершити роботу програми. Є якась причина. Ви б добре розібратися в причині, оскільки це може спричинити вас навіть за меншої кількості ниток за певних обставин, хто знає.
Метью Лунд

-6

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

Ви можете знайти більше інформації про те, як це має працювати тут: http://en.wikipedia.org/wiki/Thread_pool_pattern


1
@Pax: Це було б не перший раз, коли більшість людей не хотіли відповідати на питання (або розуміти його). Я не переживаю.
ГЕОЧЕТ

-10

Стільки ниток, як ядер CPU - це те, що я чув дуже часто.


5
@Rich, принаймні поясни, чому :-). Це правило виконується лише тоді, коли всі потоки пов'язані з процесором; вони отримують один "процесор" кожен. Коли багато потоків пов'язані вводу / виводу, зазвичай краще мати багато більше потоків, ніж 'CPU (CPU цитується, оскільки це стосується фізичних потоків виконання, наприклад, ядер).
paxdiablo

1
@Abgan, я не був у цьому впевнений, думаючи, що, можливо, Python створить "справжні" потоки ОС (працює на декількох процесорах). Якщо те, що ви говорите, є правдивим (у мене немає підстав сумніватися), то кількість процесора не має ніякого відношення - нанизування корисно лише тоді, коли більшість потоків чогось чекає (наприклад, введення / виведення DB).
paxdiablo

1
@Rich: коли (справжня) нитка, кількість процесорів має значення, оскільки ви можете запускати кілька потоків, які не чекають справді одночасно. З одним процесором працює лише один, і вигода отримується від того, що багато інших потоків чекають ресурсу, який не є процесором.
paxdiablo

1
@Pax: Ви не розумієте поняття пулових потоків, то я думаю.
GEOCHET

1
@Rich, я добре розумію нитки пулів; Схоже, я (та інші тут) також краще розумію апаратне забезпечення, ніж ви. З одним процесором може працювати лише один потік виконання, навіть якщо на процесор чекають інші. Два процесора, два можуть працювати. Якщо всі потоки чекають процесора, кількість ідеальних ниток дорівнює ...
paxdiablo
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.