Страницы

Поиск по вопросам

четверг, 18 октября 2018 г.

Параллельное выполнение функций в разных классах

Хочу разобраться, как выполнить функции в разных классах одновременно. Когда пытаюсь сделать это с помощью threading, вижу, что функции выполняются по порядку. Ниже простой пример того что я пытаюсь сделать. Вообще, я пытаюсь сделать программу, которая получает данные, записывает их в БД и одновременно рисует графики по данным из БД в онлайне. Как заставить выполняться несколько функций в разных классах одновременно не понимаю.
import threading
from threading import Thread
class f1:
def __init__(self): pass
def func1(self): for i in range(10): print 'Working111' class f2:
def __init__(self): pass
def func2(self): for i in range(10): print 'Working222'
ff1 = f1()
ff2 = f2()
if __name__ == '__main__':
Thread(target = ff1.func1()).start() Thread(target = ff2.func2()).start()
Вывод:
>>> Working111 Working111 Working111 Working111 Working111 Working111 Working111 Working111 Working111 Working111 Working222 Working222 Working222 Working222 Working222 Working222 Working222 Working222 Working222 Working222 >>>


Ответ

Как правильно заметил @Avernial, в стандартной реализации Python, CPython, имеет место быть GIL, наличие которого блокирует параллельное исполнение потоков в программах на Python. Но при этом никто не запрещает использовать для параллельности процессы.
Достаточно удобную возможность реализации запуска параллельного кода в процессах предоставляет стандартная библиотека multiprocessing. Ниже приведён пример кода, адаптированный под ваш:
from multiprocessing import Process from time import sleep
class A: def __call__(self, count=10, sleep_time=0.5): for i in range(count): print('Working class A, i=%s' % i) sleep(sleep_time)
class B: def __call__(self, count=10, sleep_time=0.5): for i in range(count): print('Working class B, i=%s' % i) sleep(sleep_time)
if __name__ == '__main__': a = A() b = B()
p1 = Process(target=a, kwargs={'sleep_time': 0.7}) p2 = Process(target=b, args=(12,)) p1.start() p2.start()
p1.join() p2.join()
Вывод может иметь примерно такой вид:
Working class A, i=0 Working class B, i=0 Working class B, i=1 Working class A, i=1 Working class B, i=2 Working class A, i=2 Working class B, i=3 Working class B, i=4 Working class A, i=3 Working class B, i=5 Working class A, i=4 Working class B, i=6 Working class A, i=5 Working class B, i=7 Working class B, i=8 Working class A, i=6 Working class B, i=9 Working class A, i=7 Working class B, i=10 Working class B, i=11 Working class A, i=8 Working class A, i=9

Замечу, что если использовать возможности пакета threading на этом же коде, т.е.
import threading import os from time import sleep
class A: def __call__(self, count=10, sleep_time=0.5): for i in range(count): print('Working class A, i=%s' % i) sleep(sleep_time)
class B: def __call__(self, count=10, sleep_time=0.5): for i in range(count): print('Working class B, i=%s' % i) sleep(sleep_time)

if __name__ == '__main__': a = A() b = B()
t1 = threading.Thread(target=a, kwargs={'sleep_time': 0.7}) t2 = threading.Thread(target=b, args=(12,)) t1.start() t2.start()
t1.join() t2.join()
то вывод будет подобный предыдущему. Объяснение этого эффекта привожу ниже.

Использовать модуль threading можно в том случае, если в какой-то момент вычисление функции прерывается, например, на считывание данных, ожидание пользовательского ввода или при вызове функции time.sleep(), так как в этих случаях GIL снимается и другие потоки имеют возможность перехватить управление.
Вот цитата из документации
CPython implementation detail: In CPython, due to the Global Interpreter Lock, only one thread can execute Python code at once (even though certain performance-oriented libraries might overcome this limitation). If you want your application to make better use of the computational resources of multi-core machines, you are advised to use multiprocessing or concurrent.futures.ProcessPoolExecutor. However, threading is still an appropriate model if you want to run multiple I/O-bound tasks simultaneously
Мой вольный перевод:
Детали реализации интерпретатора CPython: в CPython, из-за наличия GIL, может исполняться только один поток Python-кода (хоть даже некоторые библиотеки, ориентированные на производительность, и могут обходить это ограничение). Если вы хотите улучшить своё приложение, добавив ему возможность использования возможностей многоядерного компьютера, мы предлагаем вам использовать возможности пакета multiprocessing или concurrent.futures.ProcessPoolExecutor. Тем не менее, threading подходит, если вы хотите запускать множество завязанных на ввод-вывод задач одновременно

Решил привести более интересный пример сравнения работы модуля threading и multiprocessing
Покажем, что если в одном из потоков не происходит операций ввода-вывода или других, приводящих к отпусканию GIL, то передачи управления производиться не будет.
Для этого создадим два файла. В одном будет использоваться механизм модуля threading, а в другом -- multiprocessing. Код функции класса B будет выполнять продолжительные вычисления без прерывания на ввод-вывод. Весь остальной код будет одинаковым.
Файл threading_test.py:
import threading import os from time import sleep
class A: def __call__(self, count=10, sleep_time=0.5): for i in range(count): print('Working class A, i=%s' % i) sleep(sleep_time)
class B: def __call__(self, count=10, sleep_time=0.5): for i in range(count): x = 10 for j in range(50000): x *= 10 ** 9 # какая-то долгая операция print('Working class B, i=%s' % i) if __name__ == '__main__': a = A() b = B()
t1 = threading.Thread(target=a, kwargs={'sleep_time': 0.7}) t2 = threading.Thread(target=b, args=(12,)) t1.start() t2.start()
t1.join() t2.join()
Файл multiprocessing_test.py:
from multiprocessing import Process import os from time import sleep
class A: def __call__(self, count=10, sleep_time=0.5): for i in range(count): print('Working class A, i=%s' % i) sleep(sleep_time)
class B: def __call__(self, count=10, sleep_time=0.5): for i in range(count): x = 10 for j in range(50000): x *= 10 ** 9 # какая-то долгая операция print('Working class B, i=%s' % i)
if __name__ == '__main__': a = A() b = B()
p1 = Process(target=a, kwargs={'sleep_time': 0.7}) p2 = Process(target=b, args=(12,)) p1.start() p2.start()
p1.join() p2.join()
После запуска кода получаем следующий вывод у скрипта threading_test.py:
Working class A, i=0 Working class B, i=0 Working class B, i=1 Working class B, i=2 Working class B, i=3 Working class B, i=4 Working class A, i=1 Working class B, i=5 Working class B, i=6 Working class B, i=7 Working class B, i=8 Working class B, i=9 Working class B, i=10 Working class B, i=11 Working class A, i=2 Working class A, i=3 Working class A, i=4 Working class A, i=5 Working class A, i=6 Working class A, i=7 Working class A, i=8 Working class A, i=9
и у скрипта multiprocess_test.py:
Working class A, i=0 Working class A, i=1 Working class A, i=2 Working class A, i=3 Working class B, i=0 Working class A, i=4 Working class A, i=5 Working class B, i=1 Working class A, i=6 Working class A, i=7 Working class A, i=8 Working class B, i=2 Working class A, i=9 Working class B, i=3 Working class B, i=4 Working class B, i=5 Working class B, i=6 Working class B, i=7 Working class B, i=8 Working class B, i=9 Working class B, i=10 Working class B, i=11
Можно заметить, что в случае с threading функция класса B, завладев интерпретатором, практически не отпускала его (переключения возникали лишь в случае, если поток класса A успевал переключится в редкие операции вывода результатов), в то время как во втором случае функция класса A довольно быстро выполнилась и программа ожидала лишь завершения функции класса B. При этом никто уже не мешал друг другу и процессы выполнялись параллельно на разных ядрах процессора.

Комментариев нет:

Отправить комментарий