Як я можу використовувати нитки в Python?


1279

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

Як ви чітко показуєте, що завдання розділяються на багатопотокові?


31
Гарне загальне обговорення навколо цієї теми можна знайти в надзвичайно важкій проблемі Пайтона Джеффа Knupp. Підсумовуючи це, здається, що нарізка не для початківців.
Меттью Уокер

112
ха-ха, я схиляюсь до думки, що різьба для всіх, але для початківців - це не для нарізки речей :)))))
Богдан

42
Просто позначте, що люди повинні читати всі відповіді, оскільки пізніші вони, мабуть, кращі, оскільки використовуються нові мовні функції ...
Гвін Еванс

5
Не забудьте написати основну логіку в C і зателефонувати їй через ctypes, щоб дійсно скористатися потоком Python.
aaa90210

4
Я просто хотів додати, що PyPubSub - це чудовий спосіб надсилання та отримання повідомлень для контролю потоку потоків
ytpillai

Відповіді:


1417

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

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

from multiprocessing.dummy import Pool as ThreadPool
pool = ThreadPool(4)
results = pool.map(my_function, my_array)

Яка багатопотокова версія:

results = []
for item in my_array:
    results.append(my_function(item))

Опис

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

Карта обробляє для нас ітерацію послідовності, застосовує функцію та зберігає всі результати у зручному списку в кінці.

Введіть тут опис зображення


Впровадження

Паралельні версії функції карти надаються двома бібліотеками: багатопроцесорна, а також її маловідома, але не менш фантастична дочірня ступінь: multiprocessing.dummy.

multiprocessing.dummyточно такий же, як і багатопроцесорний модуль, але він використовує замість цього потоки ( важлива відмінність - використовувати декілька процесів для завдань, що займаються процесором; потоки для (і під час) вводу / виводу ):

multiprocessing.dummy копіює API багатопроцесорної обробки, але не більше ніж обгортка навколо модуля різьблення.

import urllib2
from multiprocessing.dummy import Pool as ThreadPool

urls = [
  'http://www.python.org',
  'http://www.python.org/about/',
  'http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html',
  'http://www.python.org/doc/',
  'http://www.python.org/download/',
  'http://www.python.org/getit/',
  'http://www.python.org/community/',
  'https://wiki.python.org/moin/',
]

# Make the Pool of workers
pool = ThreadPool(4)

# Open the URLs in their own threads
# and return the results
results = pool.map(urllib2.urlopen, urls)

# Close the pool and wait for the work to finish
pool.close()
pool.join()

І результати часу:

Single thread:   14.4 seconds
       4 Pool:   3.1 seconds
       8 Pool:   1.4 seconds
      13 Pool:   1.3 seconds

Передача декількох аргументів (працює так лише в Python 3.3 та пізніших версіях ):

Для передачі декількох масивів:

results = pool.starmap(function, zip(list_a, list_b))

Або передати константу та масив:

results = pool.starmap(function, zip(itertools.repeat(constant), list_a))

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

(Дякуємо користувачеві136036 за корисний коментар.)


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

25
Це навіть нитки, а не процеси? Схоже , що він намагається многопроцессность = багато-!
AturSams

72
До речі, хлопці, ви можете писати with Pool(8) as p: p.map( *whatever* )і позбуватися букерівських рядків теж.

11
@BarafuAlbino: Як корисне це, напевно, варто відзначити, що це працює лише в Python 3.3+ .
fuglede

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

714

Ось простий приклад: вам потрібно спробувати кілька альтернативних URL-адрес і повернути вміст першого для відповіді.

import Queue
import threading
import urllib2

# Called by each thread
def get_url(q, url):
    q.put(urllib2.urlopen(url).read())

theurls = ["http://google.com", "http://yahoo.com"]

q = Queue.Queue()

for u in theurls:
    t = threading.Thread(target=get_url, args = (q,u))
    t.daemon = True
    t.start()

s = q.get()
print s

Це випадок, коли введення ниток використовується як проста оптимізація: кожен підрядник чекає розв’язання URL-адреси та відповіді, щоб поставити її вміст у чергу; кожен потік є демоном (не буде тримати процес, якщо основна нитка закінчується - це частіше, ніж ні); головний потік запускає всі підрядки, робить getу черзі очікування, поки один з них зробить a put, потім випромінює результати та припиняється (що знімає будь-які підрядки, які ще можуть працювати, оскільки вони є демоновими нитками).

Належне використання потоків у Python незмінно пов'язане з операціями вводу / виводу (оскільки CPython так чи інакше не використовує декілька ядер для виконання завдань, пов’язаних з процесором, єдина причина для введення нитки - це не блокування процесу, поки чекають деякого вводу / виводу ). Черги майже незмінно найкращий спосіб обробити роботу на потоки та / або зібрати результати роботи, до речі, і вони по суті є безпечними, тому вони рятують вас від турбот про замки, умови, події, семафори та ін. -потоки координації / комунікації.


10
Ще раз дякую, MartelliBot. Я оновив приклад, щоб зачекати, коли всі URL-адреси дадуть відповідь: імпорт черги, потоки, urllib2 q = Queue.Queue () urls = '' ' a.com b.com c.com' ''. Split () urls_received = 0 def get_url (q, url): req = urllib2.Request (url) resp = urllib2.urlopen (req) q.put (resp.read ()) global urls_received urls_received + = 1 print urls_received for u in urls: t = threading.Thread (target = get_url, args = (q, u)) t.daemon = True t.start (), а q.empty () та urls_received <len (urls): s = q.get () print s
htmldrum

3
@JRM: якщо ви подивитесь на наступну відповідь нижче, я вважаю, що кращим способом дочекатися завершення потоків буде використання join()методу, оскільки це змусить основний потік чекати, поки вони будуть готові, не споживаючи процесор постійно перевірка значення. @ Алекс: дякую, саме це мені потрібно було зрозуміти, як користуватися потоками.
krs013

6
Для python3 замініть 'import urllib2' на 'import urllib.request як urllib2'. і ввести круглі дужки у друкований вислів.
Харві

5
Для python 3 замініть Queueім'я модуля на queue. Назва методу однакова.
JSmyth

2
Зауважу, що рішення роздрукує лише одну зі сторінок. Щоб надрукувати обидві сторінки з черги, просто запустіть команду ще раз: s = q.get() print s @ krs013 Вам не потрібно, joinтому що Queue.get () блокується.
Том Андерсон

256

ПРИМІТКА . Для фактичної паралелізації в Python слід використовувати модуль багатопроцесорної обробки для роздрібнення декількох процесів, які виконуються паралельно (завдяки глобальному блокуванню інтерпретатора, потоки Python забезпечують переплетення, але насправді вони виконуються послідовно, а не паралельно, і є лише корисно при переплетенні операцій вводу / виводу).

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

import threading

class SummingThread(threading.Thread):
     def __init__(self,low,high):
         super(SummingThread, self).__init__()
         self.low=low
         self.high=high
         self.total=0

     def run(self):
         for i in range(self.low,self.high):
             self.total+=i


thread1 = SummingThread(0,500000)
thread2 = SummingThread(500000,1000000)
thread1.start() # This actually causes the thread to run
thread2.start()
thread1.join()  # This waits until the thread has completed
thread2.join()
# At this point, both threads have completed
result = thread1.total + thread2.total
print result

Зауважте, що вищезазначене є дуже дурним прикладом, оскільки воно абсолютно не робить вводу-виводу і буде виконуватися послідовно, хоч і перемежоване (з доданими накладними переключеннями контексту) в CPython завдяки глобальному блокуванню інтерпретатора.


16
@ Алекс, я не сказав, що це практично, але він демонструє, як визначати і створювати породження ниток, що, на мою думку, є те, чого хоче ОП.
Майкл Аарон Сафян

6
Хоча це дійсно показує, як визначати і нереститися потоки, воно насправді не підсумовує піддіаграми паралельно. thread1працює до тих пір, поки вона не буде завершена, поки блокується основний потік, потім відбувається те саме, що відбувається thread2, тоді головний потік поновлюється та виводить значення, які вони накопичили.
мартіно

Чи не повинно бути це super(SummingThread, self).__init__()? Як у stackoverflow.com/a/2197625/806988
Джеймс Андрес

@JamesAndres, якщо припустити, що від "SummingThread" ніхто не успадковує, то або один працює добре; В такому випадку супер (SummingThread, самість) це просто химерний спосіб поглянути на наступний клас в порядку дозволу методів (MRO), який threading.Thread (а потім згодом викликаючи INIT на те , що в обох випадках). Ти маєш рацію в тому, що використання Super () - кращий стиль для поточного Python. Супер був порівняно недавно на той момент, коли я дав цю відповідь, отже, зателефонувавши безпосередньо до суперкласу, а не використовуючи super (). Я оновлю це для використання супер, хоча.
Майкл Аарон Сафян

14
ПОПЕРЕДЖЕННЯ. Не використовуйте багатопотокове читання в таких завданнях! Як показав Дейв Бізлі: dabeaz.com/python/NewGIL.pdf , 2 потоки python на 2 процесорах виконують важке для процесора завдання в 2 рази РІШНЕ, ніж 1 потік на 1 процесорі і в 1,5 рази менший за 2 потоки на 1 процесорі. Ця химерна поведінка пояснюється неправильною координацією зусиль між ОС та Python. Справа використання потоків у реальному житті - це важке завдання вводу / виводу. Наприклад, коли ви читаєте / записуєте через мережу, має сенс покласти нитку, чекаючи, коли дані будуть прочитані / записані, фон та переключення процесора на інший потік, який потребує обробки даних.
Борис Бурков

98

Як і інші згадані, CPython може використовувати потоки лише для очікування вводу / виводу завдяки GIL .

Якщо ви хочете скористатися декількома ядрами для завдань, пов'язаних з процесором, використовуйте мультиобробку :

from multiprocessing import Process

def f(name):
    print 'hello', name

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()

33
ви могли б трохи пояснити, що це робить?
пандіта

5
@pandita: код створює процес, а потім запускає його. Тож зараз відбувається дві речі: головна лінія програми та процес, що починається з цілі, fфункціонують. Паралельно основна програма тепер просто чекає завершення процесу, joinпродовжуючи його виконувати. Якщо основна частина щойно вийшла, підпроцес може або не закінчиться, тому виконувати завдання joinзавжди рекомендується.
johntellsall

1
Розгорнуту відповідь , який включає в себе mapфункцію тут: stackoverflow.com/a/28463266/2327328
philshem

2
@philshem Будьте уважні, якщо в посилання, яке ви опублікували, використовується пул потоків (а не процесів), як згадується тут stackoverflow.com/questions/26432411/… . Однак ця відповідь використовує процес. Я новачок у цьому матеріалі, але, схоже, (завдяки GIL) ви отримаєте підвищення продуктивності лише в конкретних ситуаціях, використовуючи багатопотоковість в Python. Однак, використовуючи пул процесів, можна скористатися багатоядерним процесором, якщо мати більше 1 основної роботи над процесом.
користувач3731622

3
Це найкраща відповідь за те, що насправді робити щось корисне та скористатися декількома ядрами процесора
Frobot

92

Лише зауваження: черга для нарізування різьби не потрібна.

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

import threading
from random import randint
from time import sleep


def print_number(number):

    # Sleeps a random 1 to 10 seconds
    rand_int_var = randint(1, 10)
    sleep(rand_int_var)
    print "Thread " + str(number) + " slept for " + str(rand_int_var) + " seconds"

thread_list = []

for i in range(1, 10):

    # Instantiates the thread
    # (i) does not make a sequence, so (i,)
    t = threading.Thread(target=print_number, args=(i,))
    # Sticks the thread in a list so that it remains accessible
    thread_list.append(t)

# Starts threads
for thread in thread_list:
    thread.start()

# This blocks the calling thread until the thread whose join() method is called is terminated.
# From http://docs.python.org/2/library/threading.html#thread-objects
for thread in thread_list:
    thread.join()

# Demonstrates that the main process waited for threads to complete
print "Done"

3
Додайте останню цитату до "Готово, щоб вона надрукувала" Готово "
iChux

1
Мені цей приклад подобається краще, ніж Мартеллі, з ним легше грати. Однак я б рекомендував printNumber зробити наступне, щоб зробити його трохи зрозумілішим, що відбувається: він повинен зберегти randint до змінної перед сном на ньому, а потім друк слід змінити, щоб сказати "Thread" + str ( номер) + "спав" + theRandintVariable + "секунди"
Микола

Чи є спосіб дізнатися, коли кожна нитка закінчена, як вона закінчується?
Метт

1
@Matt Є кілька способів зробити щось подібне, але це залежатиме від ваших потреб. Одним із способів було б оновлення одиночної або іншої загальнодоступної змінної, яка спостерігається в циклі часу і оновлюється в кінці потоку.
Дуглас Адамс

2
Немає потреби в другому forциклі, ви можете зателефонувати thread.start()в перший цикл.
Марк Мішин

49

Відповідь від Алекса Мартеллі мені допомогла. Однак ось модифікована версія, яку я вважав кориснішою (принаймні для мене).

Оновлено: працює як у Python 2, так і в Python 3

try:
    # For Python 3
    import queue
    from urllib.request import urlopen
except:
    # For Python 2 
    import Queue as queue
    from urllib2 import urlopen

import threading

worker_data = ['http://google.com', 'http://yahoo.com', 'http://bing.com']

# Load up a queue with your data. This will handle locking
q = queue.Queue()
for url in worker_data:
    q.put(url)

# Define a worker function
def worker(url_queue):
    queue_full = True
    while queue_full:
        try:
            # Get your data off the queue, and do some work
            url = url_queue.get(False)
            data = urlopen(url).read()
            print(len(data))

        except queue.Empty:
            queue_full = False

# Create as many threads as you want
thread_count = 5
for i in range(thread_count):
    t = threading.Thread(target=worker, args = (q,))
    t.start()

6
Чому б просто не зламати виняток?
Ставрос Корокітакіс

1
ви могли, лише особисті переваги
JimJty

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

1
@dylnmc, це поза моїм випадком використання (моя вхідна черга заздалегідь визначена). Якщо ви хочете піти своїм маршрутом, я б запропонував подивитися на селеру
JimJty

@JimJty Ви знаєте, чому я отримую цю помилку: import Queue ModuleNotFoundError: No module named 'Queue'я запускаю python 3.6.5, деякі повідомлення згадують, що в python 3.6.5 це, queueале навіть після того, як я його
змінюю

25

Дано функцію, fвведіть її так:

import threading
threading.Thread(target=f).start()

Передати аргументи до f

threading.Thread(target=f, args=(a,b,c)).start()

Це дуже прямо. Як ви гарантуєте, що нитки закриваються, коли ви закінчите з ними?
cameronroytaylor

Наскільки я розумію, коли функція виходить, Threadоб'єкт очищається. Дивіться документи . Існує is_alive()метод, за допомогою якого ви можете перевірити потік, якщо це потрібно.
starfry

Я бачив is_aliveметод, але не зміг зрозуміти, як застосувати його до теми. Я спробував призначити, thread1=threading.Thread(target=f).start()а потім перевірити його thread1.is_alive(), але thread1він заповнений None, тому не пощастило. Чи знаєте ви, чи є інший спосіб отримати доступ до потоку?
Камеронройтайлор

4
Вам потрібно призначити об’єкт потоку змінній, а потім запустити його, використовуючи той змінний: thread1=threading.Thread(target=f)далі thread1.start(). Тоді ви можете зробити thread1.is_alive().
starfry

1
Це спрацювало. І так, тестування з thread1.is_alive()поверненнями, Falseяк тільки функція закінчується.
Камеронройтайлор

25

Я вважаю це дуже корисним: створіть стільки потоків, скільки ядер, і дозвольте їм виконати (велику) кількість завдань (у цьому випадку виклик програми оболонки):

import Queue
import threading
import multiprocessing
import subprocess

q = Queue.Queue()
for i in range(30): # Put 30 tasks in the queue
    q.put(i)

def worker():
    while True:
        item = q.get()
        # Execute a task: call a shell program and wait until it completes
        subprocess.call("echo " + str(item), shell=True)
        q.task_done()

cpus = multiprocessing.cpu_count() # Detect number of cores
print("Creating %d threads" % cpus)
for i in range(cpus):
     t = threading.Thread(target=worker)
     t.daemon = True
     t.start()

q.join() # Block until all tasks are done

@shavenwarthog впевнений, що ви можете налаштувати змінну "cpus" залежно від своїх потреб. У будь-якому випадку, виклик підпроцесора породжує підпроцеси, і вони будуть виділятися cpus ОС ("батьківський процес" python не означає "той самий процесор" для підпроцесів).
дельфін

2
ви правильні, мій коментар щодо "потоки запускаються в тому ж процесорі, що і батьківський процес" невірний. Дякую за відповідь!
johntellsall

1
можливо, варто відзначити, що на відміну від багатопотокової роботи, яка використовує той самий простір пам'яті, багатопроцесорна робота не може легко обмінюватися змінними / даними. Хоча +1.
фантастичний

22

У Python 3 є можливість запускати паралельні завдання . Це полегшує нашу роботу.

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

Далі дає зрозуміти:

Приклад теми ThreadPoolExecutor ( джерело )

import concurrent.futures
import urllib.request

URLS = ['http://www.foxnews.com/',
        'http://www.cnn.com/',
        'http://europe.wsj.com/',
        'http://www.bbc.co.uk/',
        'http://some-made-up-domain.com/']

# Retrieve a single page and report the URL and contents
def load_url(url, timeout):
    with urllib.request.urlopen(url, timeout=timeout) as conn:
        return conn.read()

# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
    # Start the load operations and mark each future with its URL
    future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
    for future in concurrent.futures.as_completed(future_to_url):
        url = future_to_url[future]
        try:
            data = future.result()
        except Exception as exc:
            print('%r generated an exception: %s' % (url, exc))
        else:
            print('%r page is %d bytes' % (url, len(data)))

ProcessPoolExecutor ( джерело )

import concurrent.futures
import math

PRIMES = [
    112272535095293,
    112582705942171,
    112272535095293,
    115280095190773,
    115797848077099,
    1099726899285419]

def is_prime(n):
    if n % 2 == 0:
        return False

    sqrt_n = int(math.floor(math.sqrt(n)))
    for i in range(3, sqrt_n + 1, 2):
        if n % i == 0:
            return False
    return True

def main():
    with concurrent.futures.ProcessPoolExecutor() as executor:
        for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
            print('%d is prime: %s' % (number, prime))

if __name__ == '__main__':
    main()

18

Використання нового модуля concurrent.futures

def sqr(val):
    import time
    time.sleep(0.1)
    return val * val

def process_result(result):
    print(result)

def process_these_asap(tasks):
    import concurrent.futures

    with concurrent.futures.ProcessPoolExecutor() as executor:
        futures = []
        for task in tasks:
            futures.append(executor.submit(sqr, task))

        for future in concurrent.futures.as_completed(futures):
            process_result(future.result())
        # Or instead of all this just do:
        # results = executor.map(sqr, tasks)
        # list(map(process_result, results))

def main():
    tasks = list(range(10))
    print('Processing {} tasks'.format(len(tasks)))
    process_these_asap(tasks)
    print('Done')
    return 0

if __name__ == '__main__':
    import sys
    sys.exit(main())

Підхід виконавця може здатися знайомим всім, хто раніше забруднив руки Java.

Також зі сторони: Щоб зберегти всесвітній розум, не забудьте закрити свої пули / виконавці, якщо ви не використовуєте withконтекст (що настільки приголомшливо, що це робить для вас)


17

Для мене ідеальним прикладом для нарізання ниток є моніторинг асинхронних подій. Подивіться на цей код.

# thread_test.py
import threading
import time

class Monitor(threading.Thread):
    def __init__(self, mon):
        threading.Thread.__init__(self)
        self.mon = mon

    def run(self):
        while True:
            if self.mon[0] == 2:
                print "Mon = 2"
                self.mon[0] = 3;

Ви можете грати з цим кодом, відкриваючи сеанс IPython і виконуючи щось на кшталт:

>>> from thread_test import Monitor
>>> a = [0]
>>> mon = Monitor(a)
>>> mon.start()
>>> a[0] = 2
Mon = 2
>>>a[0] = 2
Mon = 2

Почекайте кілька хвилин

>>> a[0] = 2
Mon = 2

1
AttributeError: Об'єкт 'Monitor' не має атрибута 'stop'?
пандіта

5
Хіба ви не підриваєте цикли процесора, очікуючи на те, що відбудеться подія? Не завжди дуже практична річ.
магнат

3
Як каже магнат, це буде постійно виконуватись. Як мінімум, ви можете додати в короткий сон, скажімо, сон (0,1), що, ймовірно, значно знизить використання процесора на простому прикладі, як цей.
фантастичний

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

16

Більшість документації та навчальних посібників використовують Python Threadingі Queueмодуль, і вони можуть здатися непосильними для початківців.

Можливо, розглянемо concurrent.futures.ThreadPoolExecutorмодуль Python 3.

У поєднанні з withпунктом та розумінням списку це може бути справжньою принадою.

from concurrent.futures import ThreadPoolExecutor, as_completed

def get_url(url):
    # Your actual program here. Using threading.Lock() if necessary
    return ""

# List of URLs to fetch
urls = ["url1", "url2"]

with ThreadPoolExecutor(max_workers = 5) as executor:

    # Create threads
    futures = {executor.submit(get_url, url) for url in urls}

    # as_completed() gives you the threads once finished
    for f in as_completed(futures):
        # Get the results
        rs = f.result()

15

Я бачив тут багато прикладів, коли не виконувались реальні роботи, і вони здебільшого були пов'язані з процесором. Ось приклад завдання, пов'язаного з процесором, який обчислює всі прості числа між 10 і 10,05 мільйонами. Я тут використав усі чотири методи:

import math
import timeit
import threading
import multiprocessing
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor


def time_stuff(fn):
    """
    Measure time of execution of a function
    """
    def wrapper(*args, **kwargs):
        t0 = timeit.default_timer()
        fn(*args, **kwargs)
        t1 = timeit.default_timer()
        print("{} seconds".format(t1 - t0))
    return wrapper

def find_primes_in(nmin, nmax):
    """
    Compute a list of prime numbers between the given minimum and maximum arguments
    """
    primes = []

    # Loop from minimum to maximum
    for current in range(nmin, nmax + 1):

        # Take the square root of the current number
        sqrt_n = int(math.sqrt(current))
        found = False

        # Check if the any number from 2 to the square root + 1 divides the current numnber under consideration
        for number in range(2, sqrt_n + 1):

            # If divisible we have found a factor, hence this is not a prime number, lets move to the next one
            if current % number == 0:
                found = True
                break

        # If not divisible, add this number to the list of primes that we have found so far
        if not found:
            primes.append(current)

    # I am merely printing the length of the array containing all the primes, but feel free to do what you want
    print(len(primes))

@time_stuff
def sequential_prime_finder(nmin, nmax):
    """
    Use the main process and main thread to compute everything in this case
    """
    find_primes_in(nmin, nmax)

@time_stuff
def threading_prime_finder(nmin, nmax):
    """
    If the minimum is 1000 and the maximum is 2000 and we have four workers,
    1000 - 1250 to worker 1
    1250 - 1500 to worker 2
    1500 - 1750 to worker 3
    1750 - 2000 to worker 4
    so let’s split the minimum and maximum values according to the number of workers
    """
    nrange = nmax - nmin
    threads = []
    for i in range(8):
        start = int(nmin + i * nrange/8)
        end = int(nmin + (i + 1) * nrange/8)

        # Start the thread with the minimum and maximum split up to compute
        # Parallel computation will not work here due to the GIL since this is a CPU-bound task
        t = threading.Thread(target = find_primes_in, args = (start, end))
        threads.append(t)
        t.start()

    # Don’t forget to wait for the threads to finish
    for t in threads:
        t.join()

@time_stuff
def processing_prime_finder(nmin, nmax):
    """
    Split the minimum, maximum interval similar to the threading method above, but use processes this time
    """
    nrange = nmax - nmin
    processes = []
    for i in range(8):
        start = int(nmin + i * nrange/8)
        end = int(nmin + (i + 1) * nrange/8)
        p = multiprocessing.Process(target = find_primes_in, args = (start, end))
        processes.append(p)
        p.start()

    for p in processes:
        p.join()

@time_stuff
def thread_executor_prime_finder(nmin, nmax):
    """
    Split the min max interval similar to the threading method, but use a thread pool executor this time.
    This method is slightly faster than using pure threading as the pools manage threads more efficiently.
    This method is still slow due to the GIL limitations since we are doing a CPU-bound task.
    """
    nrange = nmax - nmin
    with ThreadPoolExecutor(max_workers = 8) as e:
        for i in range(8):
            start = int(nmin + i * nrange/8)
            end = int(nmin + (i + 1) * nrange/8)
            e.submit(find_primes_in, start, end)

@time_stuff
def process_executor_prime_finder(nmin, nmax):
    """
    Split the min max interval similar to the threading method, but use the process pool executor.
    This is the fastest method recorded so far as it manages process efficiently + overcomes GIL limitations.
    RECOMMENDED METHOD FOR CPU-BOUND TASKS
    """
    nrange = nmax - nmin
    with ProcessPoolExecutor(max_workers = 8) as e:
        for i in range(8):
            start = int(nmin + i * nrange/8)
            end = int(nmin + (i + 1) * nrange/8)
            e.submit(find_primes_in, start, end)

def main():
    nmin = int(1e7)
    nmax = int(1.05e7)
    print("Sequential Prime Finder Starting")
    sequential_prime_finder(nmin, nmax)
    print("Threading Prime Finder Starting")
    threading_prime_finder(nmin, nmax)
    print("Processing Prime Finder Starting")
    processing_prime_finder(nmin, nmax)
    print("Thread Executor Prime Finder Starting")
    thread_executor_prime_finder(nmin, nmax)
    print("Process Executor Finder Starting")
    process_executor_prime_finder(nmin, nmax)

main()

Ось результати на моїй чотирьохядерній машині Mac OS X

Sequential Prime Finder Starting
9.708213827005238 seconds
Threading Prime Finder Starting
9.81836523200036 seconds
Processing Prime Finder Starting
3.2467174359990167 seconds
Thread Executor Prime Finder Starting
10.228896902000997 seconds
Process Executor Finder Starting
2.656402041000547 seconds

1
@TheUnfunCat жоден виконавець процесу не набагато кращий, ніж нарізка для виконання завдань, пов’язаних з процесором
PirateApp,

1
Чудова відповідь чувак. Я можу підтвердити, що в Python 3.6 в Windows (принаймні) ThreadPoolExecutor не робить нічого хорошого для важких завдань процесора. Це не використання ядер для обчислення. У той час як ProcessPoolExecutor копіює дані в КОЖЕН процес, який він породжує, для великих матриць це смертельно.
Анатолій Алексєєв

1
Дуже корисний приклад, але я не розумію, як це колись працювало. Нам потрібно if __name__ == '__main__':перед головним викликом, в іншому випадку вимір розмножується сам і друкує спроба була зроблена почати новий процес до ... .
Штейн

1
@Stein Я вважаю, що це лише проблема в Windows.
AMC

12

Ось дуже простий приклад імпорту CSV з використанням ниток. (Включення бібліотеки може відрізнятися для різних цілей.)

Функції помічника:

from threading import Thread
from project import app
import csv


def import_handler(csv_file_name):
    thr = Thread(target=dump_async_csv_data, args=[csv_file_name])
    thr.start()

def dump_async_csv_data(csv_file_name):
    with app.app_context():
        with open(csv_file_name) as File:
            reader = csv.DictReader(File)
            for row in reader:
                # DB operation/query

Функція драйвера:

import_handler(csv_file_name)

9

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

У цій відповіді ви знайдете інформацію про GIL Python's (глобальний замок інтерпретатора) та простий щоденний приклад, написаний за допомогою multiprocessing.dummy плюс кілька простих орієнтирів.

Блокування глобального перекладача (GIL)

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

У Python є конструкція, яка називається глобальним блокуванням інтерпретатора (GIL). GIL гарантує, що лише один із ваших "потоків" може виконатись будь-коли. Нитка набуває GIL, виконує невелику роботу, потім передає GIL на наступну нитку.

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

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

Є причини використовувати пакет для нарізки Python. Якщо ви хочете виконувати деякі речі одночасно, а ефективність не викликає особливих проблем, то це абсолютно добре і зручно. Або якщо ви працюєте з кодом, який потребує чогось чекати (наприклад, якийсь ввід / вивід), це може мати багато сенсу. Але бібліотека з нарізками не дозволить вам використовувати додаткові ядра CPU.

Багатопотокове передавання можна передавати в операційну систему (виконуючи мультиобробку), а також деяку зовнішню програму, яка викликає ваш код Python (наприклад, Spark або Hadoop ), або якийсь код, який викликає ваш код Python (наприклад, ви могли б попросіть ваш код Python викликати функцію C, яка виконує дорогі багатопотокові речі.

Чому це має значення

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

Як тільки ця інформація стане зрозумілою, ось мій код:

#!/bin/python
from multiprocessing.dummy import Pool
from subprocess import PIPE,Popen
import time
import os

# In the variable pool_size we define the "parallelness".
# For CPU-bound tasks, it doesn't make sense to create more Pool processes
# than you have cores to run them on.
#
# On the other hand, if you are using I/O-bound tasks, it may make sense
# to create a quite a few more Pool processes than cores, since the processes
# will probably spend most their time blocked (waiting for I/O to complete).
pool_size = 8

def do_ping(ip):
    if os.name == 'nt':
        print ("Using Windows Ping to " + ip)
        proc = Popen(['ping', ip], stdout=PIPE)
        return proc.communicate()[0]
    else:
        print ("Using Linux / Unix Ping to " + ip)
        proc = Popen(['ping', ip, '-c', '4'], stdout=PIPE)
        return proc.communicate()[0]


os.system('cls' if os.name=='nt' else 'clear')
print ("Running using threads\n")
start_time = time.time()
pool = Pool(pool_size)
website_names = ["www.google.com","www.facebook.com","www.pinterest.com","www.microsoft.com"]
result = {}
for website_name in website_names:
    result[website_name] = pool.apply_async(do_ping, args=(website_name,))
pool.close()
pool.join()
print ("\n--- Execution took {} seconds ---".format((time.time() - start_time)))

# Now we do the same without threading, just to compare time
print ("\nRunning NOT using threads\n")
start_time = time.time()
for website_name in website_names:
    do_ping(website_name)
print ("\n--- Execution took {} seconds ---".format((time.time() - start_time)))

# Here's one way to print the final output from the threads
output = {}
for key, value in result.items():
    output[key] = value.get()
print ("\nOutput aggregated in a Dictionary:")
print (output)
print ("\n")

print ("\nPretty printed output: ")
for key, value in output.items():
    print (key + "\n")
    print (value)

7

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

tLock = нарізання нитки.BoundedSemaphore (значення = 4)

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

import threading
import time

#tLock = threading.Lock()
tLock = threading.BoundedSemaphore(value=4)
def timer(name, delay, repeat):
    print  "\r\nTimer: ", name, " Started"
    tLock.acquire()
    print "\r\n", name, " has the acquired the lock"
    while repeat > 0:
        time.sleep(delay)
        print "\r\n", name, ": ", str(time.ctime(time.time()))
        repeat -= 1

    print "\r\n", name, " is releaseing the lock"
    tLock.release()
    print "\r\nTimer: ", name, " Completed"

def Main():
    t1 = threading.Thread(target=timer, args=("Timer1", 2, 5))
    t2 = threading.Thread(target=timer, args=("Timer2", 3, 5))
    t3 = threading.Thread(target=timer, args=("Timer3", 4, 5))
    t4 = threading.Thread(target=timer, args=("Timer4", 5, 5))
    t5 = threading.Thread(target=timer, args=("Timer5", 0.1, 5))

    t1.start()
    t2.start()
    t3.start()
    t4.start()
    t5.start()

    print "\r\nMain Complete"

if __name__ == "__main__":
    Main()

5

Завдяки запозиченню з цієї публікації ми знаємо про вибір міжпотокової, багатопроцесорної та асинхронної / asyncioта їх використання.

У Python 3 є нова вбудована бібліотека для паралельності та паралелізму: concurrent.futures

Тому я продемонструю за допомогою експерименту виконання чотирьох завдань (тобто .sleep()методу) Threading-Poolспособом:

from concurrent.futures import ThreadPoolExecutor, as_completed
from time import sleep, time

def concurrent(max_worker=1):
    futures = []

    tick = time()
    with ThreadPoolExecutor(max_workers=max_worker) as executor:
        futures.append(executor.submit(sleep, 2))  # Two seconds sleep
        futures.append(executor.submit(sleep, 1))
        futures.append(executor.submit(sleep, 7))
        futures.append(executor.submit(sleep, 3))

        for future in as_completed(futures):
            if future.result() is not None:
                print(future.result())

    print('Total elapsed time by {} workers:'.format(max_worker), time()-tick)

concurrent(5)
concurrent(4)
concurrent(3)
concurrent(2)
concurrent(1)

Вихід:

Total elapsed time by 5 workers: 7.007831811904907
Total elapsed time by 4 workers: 7.007944107055664
Total elapsed time by 3 workers: 7.003149509429932
Total elapsed time by 2 workers: 8.004627466201782
Total elapsed time by 1 workers: 13.013478994369507

[ ПРИМІТКА ]:

  • Як видно з вищезазначених результатів, найкращим випадком було 3 працівники для цих чотирьох завдань.
  • Якщо у вас замість прив'язки вводу / виводу або блокування ( multiprocessingvs threading) у вас завдання процесу, ви можете змінити ThreadPoolExecutorна ProcessPoolExecutor.

4

Жодне з попередніх рішень фактично не використовувало декілька ядер на моєму сервері GNU / Linux (де я не маю прав адміністратора). Вони просто бігли на одне ядро.

Я використовував os.forkінтерфейс нижнього рівня для нерестування декількох процесів. Це код, який працював для мене:

from os import fork

values = ['different', 'values', 'for', 'threads']

for i in range(len(values)):
    p = fork()
    if p == 0:
        my_function(values[i])
        break

2
import threading
import requests

def send():

  r = requests.get('https://www.stackoverlow.com')

thread = []
t = threading.Thread(target=send())
thread.append(t)
t.start()

1
@sP_ Я здогадуюсь, тому що тоді у вас є об’єкти теми, щоб ви могли чекати, коли вони закінчаться.
Олександр Макрагіч

1
t = нарізання
нитки. Нитка

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