Як працюють потоки в Python, і які загальні підводні камені для потокової роботи з Python?


85

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

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

Де є гарне пояснення чи ви можете надати його? Також було б дуже приємно знати про загальні проблеми, з якими ви стикаєтесь під час використання потоків з Python.

Відповіді:


50

Так, завдяки Глобальному блоку інтерпретаторів (GIL) одночасно може запускатися лише один потік. Ось декілька посилань із деякими уявленнями про це:

З останнього посилання цікава цитата:

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

Якщо ви хочете використовувати багатоядерність, pyprocessing визначає API на основі процесу для реального розпаралелювання. PEP також включає в себе деякі цікаві тести.


1
Дійсно коментар до цитати гладкого розмаху: безумовно, потокова передача Python ефективно обмежує вас одним ядром, навіть якщо машина має кілька? Багатоядерність може мати переваги, оскільки наступний потік може бути готовим до роботи без перемикача контексту, але ваші потоки Python ніколи не можуть використовувати одночасно> 1 ядро.
James Brady

2
Правильні потоки python практично обмежені одним ядром, ОКРИ модуль C чудово взаємодіє з GIL і запускає власний власний потік.
Arafangion

Насправді, кілька ядер роблять потоки менш ефективними, оскільки багато перебоїв з перевіркою, чи кожен потік може отримати доступ до GIL. Навіть з новим GIL, продуктивність все ще гірша ... dabeaz.com/python/NewGIL.pdf
Basic

2
Зверніть увагу, що міркування щодо GIL застосовуються не до всіх перекладачів. Наскільки мені відомо, як IronPython, так і Jython функціонують без GIL, що дозволяє їх коду ефективніше використовувати багатопроцесорне обладнання. Як згадував Арафангіон, інтерпретатор CPython також може працювати належним чином багатопотоковою системою, якщо код, який не потребує доступу до елементів даних Python, звільняє блокування, а потім знову отримує його перед поверненням.
holdenweb

Що викликає перемикання контексту між потоками в Python? Це засновано на таймерних перериваннях? Блокування або конкретний виклик доходу?
CMCDragonkai

36

Python - це досить проста мова, але є застереження. Найбільше, про що потрібно знати, - це Global Interpreter Lock. Це дозволяє лише одному потоку отримати доступ до інтерпретатора. Це означає дві речі: 1) ви рідко коли-небудь виявляєте, що використовуєте оператор блокування в python і 2) якщо ви хочете скористатися перевагами багатопроцесорних систем, вам доведеться використовувати окремі процеси. EDIT: Я також повинен зазначити, що ви можете помістити частину коду в C / C ++, якщо ви хочете також обійти GIL.

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

Якщо ви хочете покращити швидкість реагування, вам слід ВДУМАТИ, використовуючи потоки. Є й інші альтернативи, а саме мікропотоки . Є також деякі основи, які слід вивчити:


@JS - Виправлено. Цей список і так був застарілим.
Джейсон Бейкер,

Мені просто здається неправильним, що вам потрібно кілька процесів - з усіма накладними витратами - щоб скористатися перевагами багатоядерної системи. У нас є кілька серверів з 32 логічними ядрами - тож мені потрібні 32 процеси, щоб ефективно їх використовувати? Madness
Basic

@Basic - Накладні витрати на запуск процесу проти запуску потоку в наші дні є мінімальними. Припускаю, що у вас можуть виникнути проблеми, якщо ми говоримо про тисячі запитів на секунду, але тоді я б поставив під сумнів вибір Python для такої зайнятої служби.
Джейсон Бейкер,

20

Нижче наведено основний зразок різьблення. Він породить 20 ниток; кожен потік виведе свій номер потоку. Запустіть його і дотримуйтесь порядку, в якому вони друкують.

import threading
class Foo (threading.Thread):
    def __init__(self,x):
        self.__x = x
        threading.Thread.__init__(self)
    def run (self):
          print str(self.__x)

for x in xrange(20):
    Foo(x).start()

Як ви вже натякали, потоки Python реалізуються за допомогою часового зрізу. Ось так вони отримують «паралельний» ефект.

У моєму прикладі мій клас Foo розширює потік, тоді я реалізую runметод, куди йде код, який ви хотіли б запустити в потоці. Для запуску потоку, який ви викликаєте start()на об'єкті потоку, який автоматично викликає runметод ...

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


10

Використовуйте потоки в python, якщо окремі працівники виконують операції з введенням / виведенням. Якщо ви намагаєтеся масштабувати кілька ядер на машині, знайдіть хороший фреймворк IPC для python або виберіть іншу мову.


4

Примітка: скрізь, де я згадую, threadя маю на увазі конкретно потоки в python, поки явно не зазначено.

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

Оскільки управління пам'яттю в Пітоні НЕ поточно-кожен потік вимагає монопольного доступу до структур даних в Пітоні interpreter.This монопольного доступ придбаний з допомогою механізму під назвою (глобальна блокування interpretr) .GIL

Why does python use GIL?

Щоб запобігти одночасному доступу кількох потоків до стану інтерпретатора та пошкодженню стану інтерпретатора.

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

Why not simply remove GIL?

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

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

So when does thread switching occurs in python?

Перемикання потоку відбувається, коли GIL відпускається. Отже, коли GIL звільняється? Є два сценарії, які слід взяти до уваги.

Якщо Thread виконує операції, пов'язані з процесором (наприклад, обробка зображень).

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

У нових версіях замість того, щоб використовувати кількість інструкцій як метрику для перемикання потоку, використовується настроюваний інтервал часу. Інтервал перемикання за замовчуванням - 5 мілісекунд. Ви можете отримати поточний інтервал перемикання за допомогою sys.getswitchinterval(). Це можна змінити за допомогоюsys.setswitchinterval()

Якщо Потік виконує певні операції з обмеженим введенням (наприклад, доступ до файлової системи або
мережевий введення-виведення)

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

Which thread to switch to next?

Інтерпретатор не має власного планувальника. Який потік стає запланованим в кінці інтервалу - це рішення операційної системи. .


3

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

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


2

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

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

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