Многопоточность в python. руководство для начинающих

Стоимость диспетчеризации уровня ядра

Сами объекты-диспетчеры, а это события, мьютексы, таймеры и семафоры — это объекты ядра. Они разделимы между процессами, и по этой причине считаются дорогими объектами.

Посмотрите на график, посмотрите на стоимость инкрементирования или доступа к кэшу L1, а затем — на стоимость присваивания объекта диспетчера ядра. Две наносекунды и 295 наносекунд — огромная разница! А создание объекта диспетчера вообще занимает 2093 наносекунды.

При разработке важно писать код, который не будут создавать объекты диспетчера лишний раз, иначе можно ожидать большую потерю производительности. На уровне .NET у нас есть , который использует внутренний таймер

возвращает выполняющийся поток обратно в очередь ожидания потоков. блокирует вызывающий поток. Но сейчас я хочу более подробно рассказать про .

Reusing Threads: QRunnable and QThreadPool

If your GUI applications rely heavily on multithreading, then you’ll face significant overhead related to creating and destroying threads. You’ll also have to consider how many threads you can start on a given system so that your applications remain efficient. Fortunately, PyQt’s thread support provides you with a solution to these issues, too.

Each application has a global thread pool. You can get a reference to it by calling .

Note: Even though using the default thread pool is a fairly common choice, you can also create your own thread pool by instantiating , which provides a collection of reusable threads.

The global thread pool maintains and manages a suggested number of threads generally based on the number of cores in your current CPU. It also handles the queuing and execution of tasks in your application’s threads. The threads in the pool are reusable, which prevents the overhead associated with creating and destroying threads.

To create tasks and run them in a thread pool, you use . This class represents a task or piece of code that needs to be run. The process of creating and executing runnable tasks involves three steps:

  1. Subclass and reimplement with the code for the task that you want to run.
  2. Instantiate the subclass of to create a runnable task.
  3. Call with the runnable task as an argument.

must contain the required code for the task at hand. The call to launches your task in one of the available threads in the pool. If there’s no available thread, then puts the task in the pool’s run queue. When a thread becomes available, the code within gets executed in that thread.

Here’s a GUI application that shows how you can implement this process in your code:

Here’s how this code works:

  • On lines 19 to 28, you subclass and reimplement with the code you want to execute. In this case, you use the usual loop for simulating a long-running task. The call to notifies you about the operation’s progress by printing a message to your terminal screen.
  • On line 52, you get the number of available threads. This number will depend on your specific hardware and is normally based on the cores of your CPU.
  • On line 53, you update the text of the label to reflect how many threads you can run.
  • On line 55, you start a loop that iterates over the available threads.
  • On line 57, you instantiate , passing the loop variable as an argument to identify the current thread. Then you call on the thread pool, using your runnable task as an argument.

It’s important to note that some of the examples in this tutorial use with a to print messages to the screen. You need to do this because , so using it might cause a mess in your output. Fortunately, the functions in are thread safe, so you can use them in multithreaded applications.

If you run this application, then you’ll get the following behavior:

When you click the Click me! button, the application launches up to four threads. In the background terminal, the application reports the progress of each thread. If you close the application, then the threads will continue running until they finish their respective tasks.

There’s no way of stopping a object from the outside in Python. To work around this, you can create a global Boolean variable and systematically check it from inside your subclasses to terminate them when your variable becomes .

Another drawback of using and is that doesn’t support signals and slots, so interthread communication can be challenging.

Время выполнения программы

Существует множество разных задач, для решения которых нужно найти время, потраченное на работу программы либо отдельных ее блоков. Чтобы найти данную величину, достаточно посчитать разницу в секундах между точкой старта определенной функции и местом, где она завершает свою работу. В следующем примере демонстрируется применение методов time() для получения текущего времени, чтобы в конечном итоге выявить, как долго работал блок кода. Метод sleep() здесь увеличивает время выполнения программы на 5 секунд.

import time
start = time.time()
time.sleep(5)
finish = time.time()
result = finish - start
print("Program time: " + str(result) + " seconds.")

Program time: 5.005090236663818 seconds.

Несмотря на простоту вышеописанного подхода, использовать его в серьезных целях, где требуется точный и независимый от ОС результат, не рекомендуется. Все дело в том, что числовое значение времени, получаемое таким образом, может иметь погрешности за счет внутренних особенностей работы компьютера, в среде которого выполняется программа. Например, системные часы могут быть подкорректированы вручную пользователем во время выполнения программы. Корректировка может произойти и автоматически, если настроена синхронизация системных часов по NTP.

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

monotonic

По этой причине лучше всего использовать метод monotonic(), впервые появившийся в версии Python 3.3 на некоторых платформах, а начиная с выпуска 3.5 ставший доступным абсолютно везде. Его главной особенностью является точное представление измеряемого количества времени, вне зависимости от работы ОС и текущей платформы. Используемый таймер никогда не вернет при повторном вызове метода значение, которое будет меньше предыдущего. Это позволяет избежать многих ошибок, а также неожиданного поведения.

import time
start = time.monotonic()
time.sleep(15)
result = time.monotonic() - start
print("Program time: {:>.3f}".format(result) + " seconds.")

Program time: 15.012 seconds.

В примере программы, описанном выше, продемонстрирована работа функции monotonic(). Принцип ее применения такой же, как и в предыдущем примере, однако здесь результат обладает гарантированной точностью и независим от внешних условий. Для отображения времени в более комфортной форме используется небольшое форматирование результата.

Барьеры памяти

Помимо оптимизации процессора, существует ещё оптимизация компилятора и среды выполнения. В контексте данной статьи под компилятором и средой я буду понимать JIT и CLR. Среда может кэшировать значения в регистрах процессора, переупорядочить операции и объединять операции записи (coalesce writes).

Например, для цикла for CLR может решить заранее рассчитать значение и вынести локальную переменную за пределы цикла. Во избежание этого, конечно же, можно использовать ключевое слово volatile, но бывают и более сложные случаи.

Иногда бывает нужно, чтобы весь код был точно выполнен до какой-либо определенной инструкции, и для этого используются барьеры памяти. Барьер памяти — это инструкция, которая реализуется процессором. В .NET она доступна с помощью вызова . И что же он делает? Вызов этого метода гарантирует, что все операции перед этим методом точно завершились.

Давайте снова вернемся к программе с двумя переменными. Предположим, что эти процессоры выполняют команды «сохранить» A и «загрузить» B. Если между этими двумя командами находится барьер памяти, то процессор сначала отправит запрос «сохранить» B, подождет, пока она не выполнится. Барьеры памяти дают возможность поставить что-то вроде чекпоинта или коммита, как в базе данных.

Есть еще одна причина использовать барьеры памяти. Если вы пишете код на 32-битной машине с использованием long, DateTime или struct, то атомарность выполнения операций может нарушаться. Это значит, что даже когда в коде записана одна инструкция, на самом деле могут произойти две операции вместо одной, причем они могут выполняться в разное время.

Multithreading in PyQt: Best Practices

There are a few best practices that you can apply when building multithreaded applications in PyQt. Here’s a non-exhaustive list:

  • Avoid launching long-running tasks in the main thread of a PyQt application.
  • Use and objects to create worker threads.
  • Use and if you need to manage a pool of worker threads.
  • Use signals and slots to establish safe interthread communication.
  • Use , , or to prevent threads from accessing shared data and resources concurrently.
  • Make sure to unlock or release , , or before finishing a thread.
  • Release the lock in all possible execution paths in functions with multiple statements.
  • Don’t try to create, access, or update GUI components or widgets from a worker thread.
  • Don’t try to move a with a parent-child relationship to a different thread.

Отличия и особенности

JavaScript пре­кра­сен тем, что его мож­но запу­стить в кон­со­ли любо­го совре­мен­но­го бра­у­зе­ра. Это для него род­ная сре­да, и JS лег­ко рабо­та­ет со стра­ни­ца­ми, объ­ек­та­ми на ней, вклад­ка­ми бра­у­зе­ра и всем, что с ним связано.

Python — более уни­вер­саль­ный язык, кото­рый рабо­та­ет не толь­ко с бра­у­зе­ра­ми, поэто­му для него нужен отдель­ный интер­пре­та­тор. Интер­пре­та­тор — это про­грам­ма, кото­рая берёт исход­ный код и выпол­ня­ет коман­ду за коман­дой. Вы може­те напи­сать отлич­ный код, но что­бы его испол­нить, вам все­гда нужен будет интерпретатор.

Есть два спо­со­ба запу­стить Python-код:

1. Поста­вить Python себе на ком­пью­тер — этот спо­соб хорош, если вы реши­ли осно­ва­тель­но изу­чить язык или про­сто люби­те, когда всё быст­ро и под кон­тро­лем. Ска­чать Python мож­но с офи­ци­аль­но­го сай­та — есть вер­сии для всех основ­ных опе­ра­ци­он­ных систем. Из мину­сов — нуж­но раз­би­рать­ся в пара­мет­рах уста­нов­ки и настрой­ки и уметь рабо­тать с команд­ной строкой. Плю­сы — пол­ный кон­троль и быстродействие.

2. Исполь­зо­вать онлайн-сервисы, напри­мер, этот: onlinegdb.com/online_python_compiler. Рабо­та­ет точ­но так же — пише­те код, нажи­ма­е­те кноп­ку Run и смот­ри­те на результат.

Мину­сы: так как это онлайн-сервис, им поль­зу­ет­ся одно­вре­мен­но мно­го чело­век, поэто­му быст­ро­дей­ствия от него ждать не сто­ит. С под­клю­че­ни­ем внеш­них моду­лей тоже могут воз­ник­нуть про­бле­мы, но с этим мож­но разо­брать­ся, если потра­тить немно­го времени.

Плюс: не нуж­но ниче­го настра­и­вать и уста­нав­ли­вать, всё рабо­та­ет сра­зу из бра­у­зе­ра. Есть под­свет­ка син­так­си­са, сооб­ще­ния об ошиб­ках и воз­мож­ность сохра­не­ния кода.

Сей­час мы напи­шем тай­мер с огляд­кой на онлай­но­вый сер­вис. А отдель­но ещё рас­ска­жем об установке.

Enumerating All Threads¶

It is not necessary to retain an explicit handle to all of the daemon
threads in order to ensure they have completed before exiting the main
process. enumerate() returns a list of active Thread
instances. The list includes the current thread, and since joining the
current thread is not allowed (it introduces a deadlock situation), it
must be skipped.

import random
import threading
import time
import logging

logging.basicConfig(level=logging.DEBUG,
                    format='(%(threadName)-10s) %(message)s',
                    )

def worker():
    """thread worker function"""
    t = threading.currentThread()
    pause = random.randint(1,5)
    logging.debug('sleeping %s', pause)
    time.sleep(pause)
    logging.debug('ending')
    return

for i in range(3):
    t = threading.Thread(target=worker)
    t.setDaemon(True)
    t.start()

main_thread = threading.currentThread()
for t in threading.enumerate():
    if t is main_thread
        continue
    logging.debug('joining %s', t.getName())
    t.join()

Since the worker is sleeping for a random amount of time, the output
from this program may vary. It should look something like this:

Thread Objects¶

The simplest way to use a Thread is to instantiate it with a
target function and call start() to let it begin working.

import threading

def worker():
    """thread worker function"""
    print 'Worker'
    return

threads = []
for i in range(5):
    t = threading.Thread(target=worker)
    threads.append(t)
    t.start()

The output is five lines with "Worker" on each:

$ python threading_simple.py

Worker
Worker
Worker
Worker
Worker

It useful to be able to spawn a thread and pass it arguments to tell
it what work to do. This example passes a number, which the thread
then prints.

import threading

def worker(num):
    """thread worker function"""
    print 'Worker: %s' % num
    return

threads = []
for i in range(5):
    t = threading.Thread(target=worker, args=(i,))
    threads.append(t)
    t.start()

The integer argument is now included in the message printed by each
thread:

Перечисления

ApartmentState

Задает апартаментное состояние потока Thread.Specifies the apartment state of a Thread.

EventResetMode

Указывает, сбрасывается ли EventWaitHandle автоматически или вручную после получения сигнала.Indicates whether an EventWaitHandle is reset automatically or manually after receiving a signal.

LazyThreadSafetyMode

Определяет, как экземпляр Lazy<T> синхронизирует доступ из нескольких потоков.Specifies how a Lazy<T> instance synchronizes access among multiple threads.

LockRecursionPolicy

Указывает, можно ли несколько раз войти в блокировку из одного и того же потока.Specifies whether a lock can be entered multiple times by the same thread.

ThreadPriority

Задает приоритет выполнения потока Thread.Specifies the scheduling priority of a Thread.

ThreadState

Задает состояния выполнения объекта Thread.Specifies the execution states of a Thread.

Синтаксис менеджера контекста

Контекстные менеджеры отлично подходят, чтобы открывать и закрывать файлы, работать с соединениями баз данных и делать многое другое, а в Python 3.10 они станут немного удобнее. Изменение позволяет в скобках указывать несколько контекстных менеджеров, что удобно, если вы хотите создать в одном операторе with несколько менеджеров:

В коде выше видно, что мы даже можем ссылаться на переменную, созданную одним контекстным менеджером (… as some_file) в следующем за ним менеджере!

Это всего лишь два из многих новых форматов в Python 3.10. Улучшенный синтаксис довольно гибок, поэтому я не буду утруждать себя и показать все возможные варианты; я почти уверен, что новый Python обработает всё, что вы ему скормите.

Synchronizing Threads¶

In addition to using Events, another way of synchronizing
threads is through using a Condition object. Because the
Condition uses a Lock, it can be tied to a shared
resource. This allows threads to wait for the resource to be updated.
In this example, the consumer() threads wait() for the
Condition to be set before continuing. The producer()
thread is responsible for setting the condition and notifying the
other threads that they can continue.

import logging
import threading
import time

logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s (%(threadName)-2s) %(message)s',
                    )

def consumer(cond):
    """wait for the condition and use the resource"""
    logging.debug('Starting consumer thread')
    t = threading.currentThread()
    with cond
        cond.wait()
        logging.debug('Resource is available to consumer')

def producer(cond):
    """set up the resource to be used by the consumer"""
    logging.debug('Starting producer thread')
    with cond
        logging.debug('Making resource available')
        cond.notifyAll()

condition = threading.Condition()
c1 = threading.Thread(name='c1', target=consumer, args=(condition,))
c2 = threading.Thread(name='c2', target=consumer, args=(condition,))
p = threading.Thread(name='p', target=producer, args=(condition,))

c1.start()
time.sleep(2)
c2.start()
time.sleep(2)
p.start()

The threads use with to acquire the lock associated with
the Condition. Using the acquire() and
release() methods explicitly also works.

Synchronizing Threads

The threading module provided with Python includes a simple-to-implement locking mechanism that allows you to synchronize threads. A new lock is created by calling the Lock() method, which returns the new lock.

The acquire(blocking) method of the new lock object is used to force threads to run synchronously. The optional blocking parameter enables you to control whether the thread waits to acquire the lock.

If blocking is set to 0, the thread returns immediately with a 0 value if the lock cannot be acquired and with a 1 if the lock was acquired. If blocking is set to 1, the thread blocks and wait for the lock to be released.

The release() method of the new lock object is used to release the lock when it is no longer required.

Example

#!/usr/bin/python

import threading
import time

class myThread (threading.Thread):
   def __init__(self, threadID, name, counter):
      threading.Thread.__init__(self)
      self.threadID = threadID
      self.name = name
      self.counter = counter
   def run(self):
      print "Starting " + self.name
      # Get lock to synchronize threads
      threadLock.acquire()
      print_time(self.name, self.counter, 3)
      # Free lock to release next thread
      threadLock.release()

def print_time(threadName, delay, counter):
   while counter:
      time.sleep(delay)
      print "%s: %s" % (threadName, time.ctime(time.time()))
      counter -= 1

threadLock = threading.Lock()
threads = []

# Create new threads
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# Start new Threads
thread1.start()
thread2.start()

# Add threads to thread list
threads.append(thread1)
threads.append(thread2)

# Wait for all threads to complete
for t in threads:
    t.join()
print "Exiting Main Thread"

When the above code is executed, it produces the following result −

Starting Thread-1
Starting Thread-2
Thread-1: Thu Mar 21 09:11:28 2013
Thread-1: Thu Mar 21 09:11:29 2013
Thread-1: Thu Mar 21 09:11:30 2013
Thread-2: Thu Mar 21 09:11:32 2013
Thread-2: Thu Mar 21 09:11:34 2013
Thread-2: Thu Mar 21 09:11:36 2013
Exiting Main Thread

Threading with queues

we use the modules threading and queue to start several threads. A queue is created by calling Queue(). Then we have an array of urls as parameter for thread construction.

Threads are created in the for loop using the call threading.Thread(). Then they have to be started by calling thead.start(). Every thread calls the method getUrl.

import queueimport threadingimport urllib.requestdef getUrl(q, url):    print('getUrl('+url+') called from a thead.')    q.put(urllib.request.urlopen(url).read())theurls = ["http://google.com", "http://google.de","http://google.ca"]threadQueue = queue.Queue()for u in theurls:    t = threading.Thread(target=getUrl, args = (threadQueue,u))    t.daemon = True    t.start()output = threadQueue.get()

Graphically this is what happens. Your program (the main thread) starts several new threads which execute a task “in parallel”.

A program can have any number of threads, each of which work on some data.

If you are a Python beginner, then I highly recommend this book.

Структуры

AsyncFlowControl

Обеспечивает функциональность для восстановления миграции или перемещения контекста выполнения между потоками.Provides the functionality to restore the migration, or flow, of the execution context between threads.

AsyncLocalValueChangedArgs<T>

Класс, предоставляющий сведения об изменениях данных экземплярам AsyncLocal<T>, которые зарегистрированы для получения уведомлений об изменениях.The class that provides data change information to AsyncLocal<T> instances that register for change notifications.

CancellationToken

Распространяет уведомление о том, что операции следует отменить.Propagates notification that operations should be canceled.

CancellationTokenRegistration

Представляет делегат обратного вызова, зарегистрированный с объектом CancellationToken.Represents a callback delegate that has been registered with a CancellationToken.

LockCookie

Определяет блокировку, которая реализует семантику «один записывающий / много читающих».Defines the lock that implements single-writer/multiple-reader semantics. Это тип значения.This is a value type.

NativeOverlapped

Содержит явно заданный макет, видимый из неуправляемого кода и имеющий тот же макет, что и структура OVERLAPPED Win32, с дополнительными зарезервированными полями в конце.Provides an explicit layout that is visible from unmanaged code and that will have the same layout as the Win32 OVERLAPPED structure with additional reserved fields at the end.

SpinLock

Предоставляет примитив взаимно исключающей блокировки, в котором поток, пытающийся получить блокировку, ожидает в состоянии цикла, проверяя доступность блокировки.Provides a mutual exclusion lock primitive where a thread trying to acquire the lock waits in a loop repeatedly checking until the lock becomes available.

SpinWait

Предоставляет поддержку ожидания на основе прокруток.Provides support for spin-based waiting.

Сопоставление шаблонов

Одна масштабная фича, о которой вы, конечно, слышали, — это структурное сопоставление шаблонов, добавляющее оператор известное выражение case из других языков. Мы знаем, как работать с case, но посмотрите на вариацию в Python это не просто switch/case, но также несколько мощных особенностей, которые мы должны исследовать.

Простое сопоставление шаблонов состоит из ключевого слова match, за которым следует выражение, а его результат проверяется на соответствие шаблонам, указанным в последовательных операторах case:

В этом простом примере мы воспользовались переменной day как выражением, которое затем сравнивается с конкретными строками в case. Кроме строк, вы также можете заметить case с маской _ — это эквивалент ключевого слова default в других языках. Хотя этот оператор можно опустить, в этом случае может произойти no-op, по существу это означает, что вернётся None.

Еще один момент, на который стоит обратить внимание в коде выше, это оператор |, позволяющий комбинировать несколько литералов | (другой его вариант — or). Как я уже упоминал, новое сопоставление шаблонов не заканчивается на базовом синтаксисе, напротив — оно привносит дополнительные возможности, например сопоставление сложных шаблонов:

Как я уже упоминал, новое сопоставление шаблонов не заканчивается на базовом синтаксисе, напротив — оно привносит дополнительные возможности, например сопоставление сложных шаблонов:

Во фрагменте выше мы воспользовались кортежем как выражением сопоставления. Однако мы не ограничены кортежами: работать будет любой итерируемый тип. Также выше видно, что маска (wildcard) _ может применяться внутри сложных шаблонов и не только сама по себе, как в предыдущих примерах. Простые кортежи или списки — не всегда лучший подход, поэтому, если вы предпочитаете классы, код можно переписать так:

Здесь видно, что с шаблонами, написанными в стиле конструкторов, можно сопоставить атрибуты класса. При использовании этого подхода отдельные атрибуты также попадают в переменные (как и в показанные ранее кортежи), с которыми затем можно работать в соответствующем операторе case.

Выше мы можем увидеть другие особенности сопоставления шаблонов: во-первых выражение в case — это гард, который также является условием в if. Это полезно, когда сопоставления по значению не достаточно и вам нужны дополнительные проверки. Посмотрите на оставшееся выражение case: видно, что и ключевые слова, (name-name) и позиционные аргументы работают с синтаксисом, похожим на синтаксис конструкторов; то же самое верно для маски _ (или отбрасываемой переменной).

Сопоставление шаблонов также позволяет работать с вложенными шаблонами. Вложенные шаблоны могут использовать любой итерируемый тип: и конструируемый объект, и несколько таких объектов, которые возможно итерировать:

В таких сложных шаблонах для дальнейшей обработки может быть полезно записать подшаблон в переменную. Это можно сделать с помощью ключевого слова as, как показано выше, во втором case.

Subclassing Thread

It is also possible to start a thread by subclassing threading.Thread. Depending on the design of your application, you may prefer this approach. Here, you extend threading.Thread and provide the implementation of your task in the run() method.

import threading
import random, time

class MyTask(threading.Thread):
    def __init__(self, sleepFor):
        self.secs = sleepFor
        threading.Thread.__init__(self)

    def run(self):
        print self, 'begin sleep(', self.secs, ')'
        time.sleep(self.secs)
        print self, 'end sleep(', self.secs, ')'

And here is the usage of the class defined above.

tasks = []
for x in xrange(0, 5):
    t = MyTask(random.randint(1, 10))
    tasks.append(t)
    t.start()

print 'joining ..'
while threading.active_count() > 1:
    for t in tasks:
        t.join()
        print t, 'is done.'
print 'all done.'

Простая блокировка в Python

Взаимоисключение (mutual exception, кратко — mutex) — простейшая блокировка, которая на время работы потока с ресурсом закрывает последний от других обращений. Реализуют это с помощью класса Lock.

import threading
mutex = threading.Lock()

Мы создали блокировку с именем mutex, но могли бы назвать её lock или иначе. Теперь её можно ставить и снимать методами .acquire() и .release():

resource = 

def thread_safe_function():
global resource
    for i in range(1000000):
        mutex.acquire()
        # Делаем что-то с переменной resource
        mutex.release()

Обратите внимание: обойти простую блокировку не может даже поток, который её активировал. Он будет заблокирован, если попытается повторно захватить ресурс, который удерживает

Изменения синтаксиса псевдонима типа

В более ранних версиях Python добавлены псевдонимы типов, позволяющие создавать синонимы пользовательских классов. В Python 3.9 и более ранних версиях псевдонимы записывались так:

Здесь FileName — псевдоним базового типа строки Python. Однако, начиная с Python 3.10, синтаксис определения псевдонимов типов будет изменён:

Благодаря этому простому изменению и программистам, и инструментам проверки типа проще отличить присваивание переменной от псевдонима. Новый синтаксис обратно совместим, так что вам не нужно обновлять старый код с псевдонимами.

Кроме этих 2 изменений появилось другое улучшение модуля typing — в предложениях по улучшению номер 612 оно называется Parameter Specification Variables. Однако это не то, что вы найдете в основной массе кода на Python, поскольку эта функциональность используется для пересылки параметра типов от одного вызываемого типа к другому вызываемому типу, например, в декораторах. Если вам есть где применить эту функциональность, посмотрите её PEP.

Usage

ThreadPooled

Mostly it is required decorator: submit function to ThreadPoolExecutor on call.

Note

API quite differs between Python 3 and Python 2.7. See API section below.

threaded.ThreadPooled.configure(max_workers=3)

Note

By default, if executor is not configured — it configures with default parameters: max_workers=CPU_COUNT * 5

@threaded.ThreadPooled
def func():
    pass

concurrent.futures.wait()

Python 3.5+ usage with asyncio:

Note

if loop_getter is not callable, loop_getter_need_context is ignored.

loop = asyncio.get_event_loop()
@threaded.ThreadPooled(loop_getter=loop, loop_getter_need_context=False)
def func():
    pass

loop.run_until_complete(asyncio.wait_for(func(), timeout))

Python 3.5+ usage with asyncio and loop extraction from call arguments:

loop_getter = lambda tgt_loop tgt_loop
@threaded.ThreadPooled(loop_getter=loop_getter, loop_getter_need_context=True)  # loop_getter_need_context is required
def func(*args, **kwargs):
    pass

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait_for(func(loop), timeout))

During application shutdown, pool can be stopped (while it will be recreated automatically, if some component will request).

threaded.ThreadPooled.shutdown()

Threaded

Classic threading.Thread. Useful for running until close and self-closing threads without return.

Usage example:

@threaded.Threaded
def func(*args, **kwargs):
    pass

thread = func()
thread.start()
thread.join()

Without arguments, thread name will use pattern: 'Threaded: ' + func.__name__

Note

If func.__name__ is not accessible, str(hash(func)) will be used instead.

Override name can be don via corresponding argument:

@threaded.Threaded(name='Function in thread')
def func(*args, **kwargs):
    pass

Thread can be daemonized automatically:

@threaded.Threaded(daemon=True)
def func(*args, **kwargs):
    pass

Also, if no any addition manipulations expected before thread start,
it can be started automatically before return:

@threaded.Threaded(started=True)
def func(*args, **kwargs):
    pass

Многопоточность на Python

За пото­ки в Python отве­ча­ет модуль threading, а сам поток мож­но создать с помо­щью клас­са Thread из это­го моду­ля. Под­клю­ча­ет­ся он так:

from threading import Thread

После это­го с помо­щью функ­ции Thread() мы смо­жем создать столь­ко пото­ков, сколь­ко нам нуж­но. Логи­ка рабо­ты такая:

  1. Под­клю­ча­ем нуж­ный модуль и класс Thread.
  2. Пишем функ­ции, кото­рые нам нуж­но выпол­нять в потоках.
  3. Созда­ём новую пере­мен­ную — поток, и пере­да­ём в неё назва­ние функ­ции и её аргу­мен­ты. Один поток = одна функ­ция на входе.
  4. Дела­ем так столь­ко пото­ков, сколь­ко тре­бу­ет логи­ка программы.
  5. Пото­ки сами сле­дят за тем, закон­чи­лась в них функ­ция или нет. Пока рабо­та­ет функ­ция — рабо­та­ет и поток.
  6. Всё это рабо­та­ет парал­лель­но и (в тео­рии) не меша­ет друг другу.

Для иллю­стра­ции запу­стим такой код:

А вот как выгля­дит резуль­тат. Обра­ти­те вни­ма­ние — пото­ки про­сы­па­ют­ся не в той после­до­ва­тель­но­сти, как мы их запу­сти­ли, а в той, в какой их выпол­нил про­цес­сор. Ино­гда это может поме­шать рабо­те про­грам­мы, но про это мы пого­во­рим отдель­но в дру­гой статье.

Подклассы потоков

При запуске Thread выполняет базовую инициализацию и затем вызывает свой метод run(). Он в свою очередь вызывает целевую функцию, переданную конструктору. Чтобы создать подкласс Thread, переопределите run().

import threading
import logging

logging.basicConfig(level=logging.DEBUG,
                    format='(%(threadName)-10s) %(message)s',
                    )

class MyThread(threading.Thread):

    def run(self):
        logging.debug('running')
        return

for i in range(5):
    t = MyThread()
    t.start()

Возвращаемое значение метода run() игнорируется.

$ python threading_subclass.py

(Thread-1  ) running
(Thread-2  ) running
(Thread-3  ) running
(Thread-4  ) running
(Thread-5  ) running

Значения args и kwargs, передаваемые в конструктор Thread, сохраняются в private переменных. Поэтому к ним трудно получить доступ из подкласса.

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

import threading
import logging

logging.basicConfig(level=logging.DEBUG,
                    format='(%(threadName)-10s) %(message)s',
                    )

class MyThreadWithArgs(threading.Thread):

    def __init__(self, group=None, target=None, name=None,
                 args=(), kwargs=None, verbose=None):
        threading.Thread.__init__(self, group=group, target=target, name=name,
                                  verbose=verbose)
        self.args = args
        self.kwargs = kwargs
        return

    def run(self):
        logging.debug('running with %s and %s', self.args, self.kwargs)
        return

for i in range(5):
    t = MyThreadWithArgs(args=(i,), kwargs={'a':'A', 'b':'B'})
    t.start()

MyThreadWithArgs использует тот же API, что и Thread. Но другой класс может легко изменить метод конструктора, чтобы принимать другие аргументы, связанные с назначением потока.

$ python threading_subclass_args.py

(Thread-1  ) running with (0,) and {'a': 'A', 'b': 'B'}
(Thread-2  ) running with (1,) and {'a': 'A', 'b': 'B'}
(Thread-3  ) running with (2,) and {'a': 'A', 'b': 'B'}
(Thread-4  ) running with (3,) and {'a': 'A', 'b': 'B'}
(Thread-5  ) running with (4,) and {'a': 'A', 'b': 'B'}
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector