Как я могу использовать многопоточность в Python?


1281

Я пытаюсь понять потоки в Python. Я посмотрел на документацию и примеры, но, честно говоря, многие примеры слишком сложны, и мне трудно их понять.

Как вы четко показываете задачи, разделенные для многопоточности?


31
Хорошее общее обсуждение этой темы можно найти в « Самой сложной проблеме » Питона Джеффа Кнуппа. Подводя итог, кажется, что потоки не для начинающих.
Мэтью Уокер

112
хаха, я склонен думать, что многопоточность предназначена для всех, но новички не для многопоточности :)))))
Богдан

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

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

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

Ответы:


1418

С тех пор, как этот вопрос был задан в 2010 году, произошло реальное упрощение того, как сделать простую многопоточность с помощью Python с map и pool .

Приведенный ниже код взят из статьи / поста в блоге, который вы обязательно должны проверить (без принадлежности) - Параллелизм в одной строке: лучшая модель для повседневных задач многопоточности . Я подведу итог ниже - это всего лишь несколько строк кода:

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))

Описание

Map - маленькая классная функция, и ключ к легкому внедрению параллелизма в ваш код Python. Для тех, кто незнаком, map - это нечто, взятое из функциональных языков, таких как Lisp. Это функция, которая отображает другую функцию в последовательности.

Map обрабатывает для нас итерации последовательности, применяет функцию и сохраняет все результаты в удобном списке в конце.

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


Реализация

Параллельные версии функции map предоставляются двумя библиотеками: multiprocessing, а также ее малоизвестным, но не менее фантастическим дочерним элементом: 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, вы можете передать несколько аргументов через этот обходной путь ).

(Спасибо user136036 за полезный комментарий.)


90
Это только не хватает голосов, потому что это так свежо опубликовано. Этот ответ прекрасно работает и демонстрирует функциональность «карты», которая дает намного более легкий для понимания синтаксис, чем другие ответы здесь.
без дела

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в очереди ожидание, пока один из них не 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 ()) глобальный urls_received urls_received + = 1 вывести urls_received для u в URL: 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 as 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», то любой из них работает нормально; в таком случае super (SummingThread, self) - это просто причудливый способ поиска следующего класса в порядке разрешения методов (MRO), который является threading.Thread (и последующего вызова init для этого в обоих случаях). Однако вы правы в том, что использование super () - лучший стиль для текущего Python. Super был относительно недавним в то время, когда я давал этот ответ, следовательно, вызывая непосредственно к суперклассу, а не используя 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всегда рекомендуется делать a .
Джонтеллсолл

1
Расширенный ответ, включающий эту mapфункцию, находится здесь: stackoverflow.com/a/28463266/2327328
philshem

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

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 Есть несколько способов сделать что-то подобное, но это будет зависеть от ваших потребностей. Один из способов - обновить одноэлементную или другую общедоступную переменную, которая просматривается в цикле while и обновляется в конце потока.
Дуглас Адамс

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

1
@dylnmc, это за пределами моего варианта использования (моя очередь ввода предопределена). Если вы хотите пойти своим путем, я бы посоветовал посмотреть на сельдерей
JimJty

@JimJty Знаете ли вы, почему я получаю эту ошибку: import Queue ModuleNotFoundError: No module named 'Queue'я запускаю python 3.6.5, в некоторых публикациях упоминается, что в python 3.6.5 это так, queueно даже после его изменения все равно не работает
user9371654

25

Учитывая функцию, fпроделайте это так:

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

Чтобы передать аргументы f

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

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

Насколько я понимаю, при выходе из функции Threadобъект очищается. Смотрите документы . Существует is_alive()метод, который вы можете использовать для проверки потока, если вам нужно.
Старфри

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

4
Вам необходимо присвоить объект потока переменной, а затем запустить его, используя эту переменную: thread1=threading.Thread(target=f)затем thread1.start(). Тогда вы можете сделать thread1.is_alive().
Старфри

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» в зависимости от своих потребностей. В любом случае, вызов подпроцесса порождает подпроцессы, и они будут выделяться процессором ОС («родительский процесс» в python не означает «тот же процессор» для подпроцессов).
Дельфин

2
Вы правы, мой комментарий о "потоках запускается на том же процессоре, что и родительский процесс" неверен. Спасибо за ответ!
Джонтеллсолл

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: у объекта «Монитор» нет атрибута «стоп»?
Пандита

5
Разве вы не взрываете циклы процессора, ожидая, когда произойдет ваше событие? Не всегда очень практичная вещь.
Магнат

3
Как говорит магнат, это будет выполняться постоянно. Как минимум, вы можете добавить в короткий сон, скажем, sleep (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
@ Штейн Я считаю, что это проблема только для 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 (глобальная блокировка интерпретатора) и простой пример из повседневной жизни, написанный с использованием multiprocessing.dummy, а также несколько простых тестов.

Глобальная блокировка интерпретатора (GIL)

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

У Python есть конструкция, называемая глобальной блокировкой интерпретатора (GIL). GIL гарантирует, что только один из ваших «потоков» может выполняться одновременно. Поток получает GIL, выполняет небольшую работу, а затем передает GIL следующему потоку.

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

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

Есть причины использовать пакет потоков Python. Если вы хотите запускать некоторые вещи одновременно, а эффективность не имеет значения, тогда это совершенно нормально и удобно. Или, если вы выполняете код, которому нужно что-то ждать (например, какой-то ввод-вывод), тогда это может иметь большой смысл. Но библиотека потоков не позволит вам использовать дополнительные ядра процессора.

Многопоточность может быть передана на аутсорсинг операционной системе (посредством многопроцессорной обработки) и некоторому внешнему приложению, которое вызывает ваш код 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 = threading.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 рабочих для этих четырех задач.
  • Если у вас есть задача процесса вместо ввода / вывод связан или блокировки ( multiprocessingпротив 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 = threading.Thread (target = send ()) должно быть t = threading.Thread (target = send)
TRiNE

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