Рубрики
Без рубрики

Низкоуровневая поддержка потоков

Автор оригинала: Doug Hellmann.

sys включает низкоуровневые функции для управления и отладки поведения потоков.

Интервал переключения

Python 3 использует глобальную блокировку, чтобы отдельные потоки не могли повредить состояние интерпретатора. После настраиваемого интервала времени выполнение байт-кода приостанавливается, и интерпретатор проверяет, нужно ли запускать какие-либо обработчики сигналов. Во время той же проверки глобальная блокировка интерпретатора (GIL) также снимается текущим потоком, а затем повторно устанавливается, при этом другим потокам предоставляется приоритет над потоком, который только что снял блокировку.

Интервал переключения по умолчанию составляет 5 миллисекунд, а текущее значение всегда можно получить с помощью sys.getswitchinterval () . Изменение интервала с помощью sys.setswitchinterval () может повлиять на производительность приложения в зависимости от характера выполняемых операций.

sys_switchinterval.py

import sys
import threading
from queue import Queue


def show_thread(q):
    for i in range(5):
        for j in range(1000000):
            pass
        q.put(threading.current_thread().name)
    return


def run_threads():
    interval  sys.getswitchinterval()
    print('interval = {:0.3f}'.format(interval))
    q  Queue()
    threads  [
        threading.Thread(targetshow_thread,
                         name'T{}'.format(i),
                         args(q,))
        for i in range(3)
    ]
    for t in threads:
        t.setDaemon(True)
        t.start()
    for t in threads:
        t.join()
    while not q.empty():
        print(q.get(), end' ')
    print()
    return


for interval in [0.001, 0.1]:
    sys.setswitchinterval(interval)
    run_threads()
    print()

Когда интервал переключения меньше, чем время, необходимое потоку для выполнения до завершения, интерпретатор передает другой элемент управления потоком, чтобы он работал некоторое время. Это показано в первом наборе выходных данных, где интервал установлен на 1 миллисекунду.

Для более длительных интервалов активный поток сможет выполнить больше работы, прежде чем он будет вынужден освободить управление. Это иллюстрируется порядком значений имен в очереди во втором примере с интервалом в 10 миллисекунд.

$ python3 sys_switchinterval.py

interval = 0.001
T0 T1 T2 T1 T0 T2 T0 T1 T2 T1 T0 T2 T1 T0 T2

interval = 0.100
T0 T0 T0 T0 T0 T1 T1 T1 T1 T1 T2 T2 T2 T2 T2

Многие факторы, помимо интервала переключения, могут управлять поведением переключения контекста потоков Python. Например, когда поток выполняет ввод-вывод, он освобождает GIL и, следовательно, может позволить другому потоку взять на себя выполнение.

Отладка

Выявление взаимоблокировок может быть одним из самых сложных аспектов работы с потоками. sys._current_frames () может помочь, точно показывая, где остановлен поток.

sys_current_frames.py

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import sys
import threading
import time

io_lock  threading.Lock()
blocker  threading.Lock()


def block(i):
    t  threading.current_thread()
    with io_lock:
        print('{} with ident {} going to sleep'.format(
            t.name, t.ident))
    if i:
        blocker.acquire()  # acquired but never released
        time.sleep(0.2)
    with io_lock:
        print(t.name, 'finishing')
    return


# Create and start several threads that "block"
threads  [
    threading.Thread(targetblock, args(i,))
    for i in range(3)
]
for t in threads:
    t.setDaemon(True)
    t.start()

# Map the threads from their identifier to the thread object
threads_by_ident  dict((t.ident, t) for t in threads)

# Show where each thread is "blocked"
time.sleep(0.01)
with io_lock:
    for ident, frame in sys._current_frames().items():
        t  threads_by_ident.get(ident)
        if not t:
            # Main thread
            continue
        print('{} stopped in {} at line {} of {}'.format(
            t.name, frame.f_code.co_name,
            frame.f_lineno, frame.f_code.co_filename))

Словарь, возвращаемый функцией sys._current_frames () , привязан к идентификатору потока, а не к его имени. Требуется небольшая работа, чтобы сопоставить эти идентификаторы с объектом потока.

Поскольку Thread-1 не находится в спящем режиме, он завершает свою работу до проверки его статуса. Поскольку он больше не активен, он не отображается в выводе. Thread-2 получает блокировку blocker , а затем ненадолго засыпает. Тем временем Thread-3 пытается получить blocker , но не может, потому что Thread-2 уже имеет его.

$ python3 sys_current_frames.py

Thread-1 with ident 123145307557888 going to sleep
Thread-1 finishing
Thread-2 with ident 123145307557888 going to sleep
Thread-3 with ident 123145312813056 going to sleep
Thread-3 stopped in block at line 18 of sys_current_frames.py
Thread-2 stopped in block at line 19 of sys_current_frames.py

Смотрите также

  • threading – модуль threading включает классы для создания потоков Python.
  • Queue – модуль Queue обеспечивает поточно-ориентированную реализацию структуры данных FIFO.
  • Переработка GIL – письмо от Антуана Питру в список рассылки python-dev с описанием реализация GIL изменяется, чтобы ввести интервал переключения.