Страницы

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

четверг, 12 декабря 2019 г.

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

#python #многопоточность


Хочу разобраться, как выполнить функции в разных классах одновременно.
Когда пытаюсь сделать это с помощью 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
>>>

    


Ответы

Ответ 1



Как правильно заметил @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. При этом никто уже не мешал друг другу и процессы выполнялись параллельно на разных ядрах процессора.

Ответ 2



Проблема в вопросе не имеет никакого отношения к GIL (как это обычно бывает в таких случаях). В Питоне, если f это функция, то f() синтаксис вызывает эту функцию, а результатом f() выражения является возвращаемое значение (None в вашем случае). Код у вас выполняется последовательно, потому что обе функции вызываются в главном потоке. Ваш код эквивалентен: ff1.func1() Thread(target=None).start() ff2.func2() Thread(target=None).start() Thread(target=None).start() создаёт новый поток, который сразу же завершается. Чтобы выполнять функции параллельно в вашем случае, просто сотрите (): Thread(target=ff1.func1).start() Thread(target=ff2.func2).start() Без (), вы передаёте объекты-функции, а не их возвращаемые значения. Чтобы увидеть, что вывод из func1 и func2 идёт вперемежку, увеличьте количество итераций, если первый поток успевает завершиться до того как второй запустился. Пример вывода: Working111 Working222Working111 Working111 Working222 Working111 Working222 Working111Working222 Working222 Working111Working222 Working222 Working111Working222 Working222 Working111 Working222 Working111 Working222 Working111 Working222 Working111 Working222 Working111 Working222 Working111 Working222 Working111 Working222 Working111 Working222 Working111 Working222 Working111 Working222Working111 Working111 Working222 ... print не является thread-safe, поэтому смешивание вывода даже на одной строчке является ожидаемым. В некоторых случаях, могут даже добавляться новые строчки.

Ответ 3



Модуль threading в данном случае вам не поможет из-за того что в python используется GIL. Для параллельного выполнения можно использовать модули multiprocessing или concurrent.

Ответ 4



Я решил проблему параллельного выполнения так: from time import sleep from threading import Thread class a: def aaa(self): self.num = 0 while True: print 'aaa', self.num sleep(1) self.num += 1 class b: def bbb(self): self.num = 0 while True: print 'bbb', self.num sleep(2) self.num += 1 class c: def ccc(self): self.num = 0 while True: print 'ccc', self.num sleep(3) self.num += 1 ap = a() aa = Thread(target=ap.aaa, args=()) bp = b() bb = Thread(target=bp.bbb, args=()) cp = c() cc = Thread(target=cp.ccc, args=()) aa.start() bb.start() cc.start() aa.join() bb.join() cc.join() print'111' print'111' не выполнится никогда так как .join() блокирует выполнение основного потока, а в методе классов вечный цикл

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

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