багатопотоковість python дочекайтеся завершення всіх потоків


119

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

Я написав сценарій Python (скажімо: scriptA.py) та сценарій (скажімо scriptB.py)

У scriptB я хочу викликати scriptA кілька разів з різними аргументами, кожен раз, коли для запуску потрібно близько години, (величезний сценарій, багато чого .. scriptA з усіма різними аргументами одночасно, але мені потрібно почекати, поки ВСІ з них будуть виконані, перш ніж продовжувати; мій код:

import subprocess

#setup
do_setup()

#run scriptA
subprocess.call(scriptA + argumentsA)
subprocess.call(scriptA + argumentsB)
subprocess.call(scriptA + argumentsC)

#finish
do_finish()

Я хочу запустити все subprocess.call()одночасно, а потім чекати, поки вони все закінчать , як мені це зробити?

Я намагався використати нитки, як приклад тут :

from threading import Thread
import subprocess

def call_script(args)
    subprocess.call(args)

#run scriptA   
t1 = Thread(target=call_script, args=(scriptA + argumentsA))
t2 = Thread(target=call_script, args=(scriptA + argumentsB))
t3 = Thread(target=call_script, args=(scriptA + argumentsC))
t1.start()
t2.start()
t3.start()

Але я не думаю, що це правильно.

Звідки я знаю, що вони закінчили бігати перед тим, як перейти до свого do_finish()?

Відповіді:


150

Вам потрібно користуватися приєднатися метод Threadоб'єкта в кінці сценарію.

t1 = Thread(target=call_script, args=(scriptA + argumentsA))
t2 = Thread(target=call_script, args=(scriptA + argumentsB))
t3 = Thread(target=call_script, args=(scriptA + argumentsC))

t1.start()
t2.start()
t3.start()

t1.join()
t2.join()
t3.join()

Таким чином, основний потік буде чекати , поки t1, t2іt3 закінчити виконання.


5
хммм - у вас є проблеми з розумінням чогось, не будете цього першого запуску t1, дочекайтеся його закінчення, потім перейдіть до t2..etc тощо? як зробити це все відразу? я не бачу, як це би запустило їх одночасно?
Інбар Роуз

25
Виклик до joinблоків, поки потік не закінчить виконання. Вам доведеться почекати всі теми в будь-якому випадку. Якщо t1закінчиться спочатку, ви почнете чекати t2(що, можливо, вже буде закінчено, і ви негайно продовжуєте чекати t3). Якщо t1потрібно тривати найдовше, коли ви повернетеся з нього обох t1і t2повернетесь негайно, не блокуючи.
Максим Скуридзін

1
Ви не розумієте мого запитання - якщо я скопіюю вказаний вище код у свій код - чи буде він працювати? чи я щось пропускаю?
Інбар Роуз

2
добре, я бачу. тепер я розумію, був трохи заплутаний з цього приводу, але я думаю, я розумію, joinначебто приєднує поточний процес до потоку і чекає, поки його виконано, і якщо t2 закінчиться до t1, то коли t1 буде зроблено, він перевірить, чи робиться t2, див. що це так, а потім перевірте t3..etc..etc .. і тоді лише тоді, коли все буде зроблено, це продовжиться. приголомшливий
Inbar Rose

3
скажімо, t1 займає найдовше, але t2 має виняток. що станеться тоді? ви можете зловити цей виняток або перевірити, чи закінчено t2 добре чи ні?
Ciprian Tomoiagă

174

Покладіть теми в список, а потім скористайтеся методом приєднання

 threads = []

 t = Thread(...)
 threads.append(t)

 ...repeat as often as necessary...

 # Start all threads
 for x in threads:
     x.start()

 # Wait for all of them to finish
 for x in threads:
     x.join()

1
Так, це спрацювало б, але важче зрозуміти. Ви завжди повинні намагатися знайти баланс між компактним кодом і "читабельністю". Пам'ятайте: Код пишеться один раз, але читається багато разів. Тому важливіше, щоб це було легко зрозуміти.
Аарон Дігулла

2
"Фабрична схема" - це не те, що я можу пояснити одним реченням. Google для цього та пошук stackoverflow.com. Прикладів та пояснень багато. Коротше кажучи: ви пишете код, який створює щось складне для вас. Як справжня фабрика: Ви даруєте замовлення і отримуєте готовий продукт назад.
Аарон Дігулла

18
Мені не подобається використовувати поняття списку для його побічних ефектів і не робити нічого корисного з отриманим списком. Простий цикл був би чистішим, навіть якщо він поширюється на два ряди ...
Іоан Олександру Куку

1
@Aaron DIgull Я це розумію. Що я маю на увазі, що я б просто зробив, for x in threads: x.join()а не скориставшись списком
Іоан Олександру Куку

1
@IoanAlexandruCucu: Я до сих пір цікаво , якщо є більш читабельним і ефективне рішення: stackoverflow.com/questions/21428602 / ...
Аарон Digulla

29

У Python3, оскільки в Python 3.2 існує новий підхід до досягнення того ж результату, я особисто віддаю перевагу традиційному створенню потоків / start / join, пакет concurrent.futures: https://docs.python.org/3/library/concurrent.futures .html

Використання ThreadPoolExecutorкоду було б:

from concurrent.futures.thread import ThreadPoolExecutor
import time

def call_script(ordinal, arg):
    print('Thread', ordinal, 'argument:', arg)
    time.sleep(2)
    print('Thread', ordinal, 'Finished')

args = ['argumentsA', 'argumentsB', 'argumentsC']

with ThreadPoolExecutor(max_workers=2) as executor:
    ordinal = 1
    for arg in args:
        executor.submit(call_script, ordinal, arg)
        ordinal += 1
print('All tasks has been finished')

Вихід попереднього коду приблизно такий:

Thread 1 argument: argumentsA
Thread 2 argument: argumentsB
Thread 1 Finished
Thread 2 Finished
Thread 3 argument: argumentsC
Thread 3 Finished
All tasks has been finished

Однією з переваг є те, що ви можете керувати пропускною здатністю, встановлюючи максимум одночасно працюючих.


але як ви можете сказати, коли всі потоки в нитці закінчено?
Prime By Design

1
Як ви бачите в прикладі, код після виконання withоператора, коли всі завдання закінчені.
Роберто

це не працює. Спробуйте зробити щось дійсно довго в нитках. Ваша заява про друк буде
виконана

@Pranalee, Цей код працює, я оновив код, щоб додати вихідні рядки. Ви не можете побачити "Всі завдання ..." до того, як всі потоки будуть закінчені. Ось як withпрацює оператор, розроблений дизайном у цьому випадку. У будь-якому випадку, ви завжди можете відкрити нове запитання в SO та опублікувати свій код, щоб ми могли допомогти вам з’ясувати, що відбувається у вашому випадку.
Роберто

@PrimeByDesign ви можете використовувати concurrent.futures.waitфункцію, реальний приклад ви можете побачити тут Офіційні документи: docs.python.org/3/library/…
Олександр Фортін

28

Я вважаю за краще розуміння списку на основі вхідного списку:

inputs = [scriptA + argumentsA, scriptA + argumentsB, ...]
threads = [Thread(target=call_script, args=(i)) for i in inputs]
[t.start() for t in threads]
[t.join() for t in threads]

Перевірена відповідь добре пояснює, але ця коротша і не потребує некрасивих повторів. Просто приємна відповідь. :)
tleb

Розуміння списку лише для побічних ефектів, як правило, знецінюється *. Але в цьому випадку використання це здається гарною ідеєю. * Stackoverflow.com/questions/5753597 / ...
Вінаяк Kaniyarakkal

3
@VinayakKaniyarakkal, for t in threads:t.start()чи не краще?
SmartManoj

5

Ви можете мати клас щось на зразок нижче, з якого ви можете додати «n» кількість функцій або console_scripts, які ви хочете виконати паралельно пристрасно, і запустити виконання та дочекатися завершення всіх завдань ..

from multiprocessing import Process

class ProcessParallel(object):
    """
    To Process the  functions parallely

    """    
    def __init__(self, *jobs):
        """
        """
        self.jobs = jobs
        self.processes = []

    def fork_processes(self):
        """
        Creates the process objects for given function deligates
        """
        for job in self.jobs:
            proc  = Process(target=job)
            self.processes.append(proc)

    def start_all(self):
        """
        Starts the functions process all together.
        """
        for proc in self.processes:
            proc.start()

    def join_all(self):
        """
        Waits untill all the functions executed.
        """
        for proc in self.processes:
            proc.join()


def two_sum(a=2, b=2):
    return a + b

def multiply(a=2, b=2):
    return a * b


#How to run:
if __name__ == '__main__':
    #note: two_sum, multiply can be replace with any python console scripts which
    #you wanted to run parallel..
    procs =  ProcessParallel(two_sum, multiply)
    #Add all the process in list
    procs.fork_processes()
    #starts  process execution 
    procs.start_all()
    #wait until all the process got executed
    procs.join_all()

Це багатопроцесорна. Питання стосувалося docs.python.org/3/library/threading.html
Рустам А.

3

З threading документації модуля

Є об'єкт "основна нитка"; це відповідає початковій нитці управління в програмі Python. Це не демонова нитка.

Існує можливість створення «макетних предметів». Це об'єкти потоку, що відповідають "чужорідним потокам", це потоки управління, розпочаті поза модулем різьблення, наприклад, безпосередньо з коду С. Об'єкти пустушкової нитки мають обмежену функціональність; їх завжди вважають живими і демонічними, і їх не можна join()редагувати. Вони ніколи не видаляються, оскільки неможливо виявити закінчення чужорідних ниток.

Отже, щоб знайти ці два випадки, коли вам не цікаво зберігати список створених потоків:

import threading as thrd


def alter_data(data, index):
    data[index] *= 2


data = [0, 2, 6, 20]

for i, value in enumerate(data):
    thrd.Thread(target=alter_data, args=[data, i]).start()

for thread in thrd.enumerate():
    if thread.daemon:
        continue
    try:
        thread.join()
    except RuntimeError as err:
        if 'cannot join current thread' in err.args[0]:
            # catchs main thread
            continue
        else:
            raise

Після чого:

>>> print(data)
[0, 4, 12, 40]

2

Можливо, щось на кшталт

for t in threading.enumerate():
    if t.daemon:
        t.join()

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

1

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

for t in threading.enumerate():
    try:
        t.join()
    except RuntimeError as err:
        if 'cannot join current thread' in err:
            continue
        else:
            raise
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.