Модуль threading на примерах
Содержание:
- Взаимодействие потоков
- «Голое» исключение
- 11.7. Tools for Working with Lists¶
- Daemon vs. Non-Daemon Threads¶
- High-level Module Interface¶
- Как работают процессы
- Limiting Concurrent Access to Resources¶
- Applications
- Объект Condition
- The Threading Module
- История 1: Task.Delay & TimerQueue
- Subclassing Thread¶
- Использование потоков
- 11.4. Multi-threading¶
Взаимодействие потоков
В многопоточной среде часто возникают задачи, требующие приостановки и возобновления работы одних потоков в зависимости от работы других. В частности это задачи, связанные с предотвращенем конфликтов доступа при использовании одних и тех же данных или устройств из параллельно исполняемых потоков. Для решения таких задач используются специальные объекты для взаимодействия потоков, такие как взаимоисключения (мьютексы), семафоры, критические секции, события и т.п. Многие из этих объектов являются объектами ядра и могут применяться не только между потоками одного процесса, но и для взаимодействия между потоками разных процессов.
- Взаимоисключения (mutex, мьютекс) — это объект синхронизации, который устанавливается в особое сигнальное состояние, когда не занят каким-либо потоком. Только один поток владеет этим объектом в любой момент времени, отсюда и название таких объектов (от английского mutually exclusive access — взаимно исключающий доступ) — одновременный доступ к общему ресурсу исключается. После всех необходимых действий мьютекс освобождается, предоставляя другим потокам доступ к общему ресурсу. Объект может поддерживать рекурсивный захват второй раз тем же потоком, увеличивая счётчик, не блокируя поток, и требуя потом многократного освобождения. Такова, например, критическая секция в Win32. Тем не менее, есть и такие реализации, которые не поддерживают такое и приводят к Взаимная блокировка|взаимной блокировке потока при попытке рекурсивного захвата. Например, это FAST_MUTEX в ядре Windows.
- Семафоры представляют собой доступные ресурсы, которые могут быть приобретены несколькими потоками в одно и то же время, пока пул ресурсов не опустеет. Тогда дополнительные потоки должны ждать, пока требуемое количество ресурсов не будет снова доступно.
- ERESOURCE. Мьютекс, поддерживающий рекурсивный захват, с семантикой разделяемого или эксклюзивного захвата. Семантика: объект может быть либо свободен, либо захвачен произвольным числом потоков разделяемым образом, либо захвачен всего одним потоком эксклюзивным образом. Любые попытки осуществить захваты, нарушающее это правило, приводят к блокировке потока до тех пор, пока объект не освободится так, чтобы сделать захват разрешённым. Также есть операции вида TryToAcquire — никогда не блокирует поток, либо захватывает, либо (если нужна блокировка) возвращает FALSE, ничего не делая. Используется в ядре Windows, особенно в файловых системах — так, например, любому кем-то открытому дисковому файлу соответствует структура FCB, в которой есть 2 таких объекта для синхронизации доступа к размеру файла. Один из них — paging IO resource — захватывается эксклюзивно только в пути обрезания файла, и гарантирует, что в момент обрезания на файле нет активного ввода-вывода от кэша и от отображения в память.
«Голое» исключение
Есть еще один способ поймать ошибку:
Python
try:
1 / 0
except:
print(«You cannot divide by zero!»)
1 |
try 1 except print(«You cannot divide by zero!») |
Но мы его не рекомендуем. На жаргоне Пайтона, это известно как голое исключение, что означает, что будут найдены вообще все исключения. Причина, по которой так делать не рекомендуется, заключается в том, что вы не узнаете, что именно за исключение вы выловите. Когда у вас возникло что-то в духе ZeroDivisionError, вы хотите выявить фрагмент, в котором происходит деление на ноль. В коде, написанном выше, вы не можете указать, что именно вам нужно выявить. Давайте взглянем еще на несколько примеров:
Python
my_dict = {«a»:1, «b»:2, «c»:3}
try:
value = my_dict
except KeyError:
print(«That key does not exist!»)
1 |
my_dict={«a»1,»b»2,»c»3} try value=my_dict»d» exceptKeyError print(«That key does not exist!») |
Python
my_list =
try:
my_list
except IndexError:
print(«That index is not in the list!»)
1 |
my_list=1,2,3,4,5 try my_list6 exceptIndexError print(«That index is not in the list!») |
В первом примере, мы создали словарь из трех элементов. После этого, мы попытались открыть доступ ключу, которого в словаре нет. Так как ключ не в словаре, возникает KeyError, которую мы выявили. Второй пример показывает список, длина которого состоит из пяти объектов. Мы попытались взять седьмой объект из индекса.
Помните, что списки в Пайтоне начинаются с нуля, так что когда вы говорите 6, вы запрашиваете 7. В любом случае, в нашем списке только пять объектов, по этой причине возникает IndexError, которую мы выявили. Вы также можете выявить несколько ошибок за раз при помощи одного оператора. Для этого существует несколько различных способов. Давайте посмотрим:
Python
my_dict = {«a»:1, «b»:2, «c»:3}
try:
value = my_dict
except IndexError:
print(«This index does not exist!»)
except KeyError:
print(«This key is not in the dictionary!»)
except:
print(«Some other error occurred!»)
1 |
my_dict={«a»1,»b»2,»c»3} try value=my_dict»d» exceptIndexError print(«This index does not exist!») exceptKeyError print(«This key is not in the dictionary!») except print(«Some other error occurred!») |
Это самый стандартный способ выявить несколько исключений. Сначала мы попробовали открыть доступ к несуществующему ключу, которого нет в нашем словаре. При помощи try/except мы проверили код на наличие ошибки KeyError, которая находится во втором операторе except
Обратите внимание на то, что в конце кода у нас появилась «голое» исключение. Обычно, это не рекомендуется, но вы, возможно, будете сталкиваться с этим время от времени, так что лучше быть проинформированным об этом
Кстати, также обратите внимание на то, что вам не нужно использовать целый блок кода для обработки нескольких исключений. Обычно, целый блок используется для выявления одного единственного исключения. Изучим второй способ выявления нескольких исключений:
Python
try:
value = my_dict
except (IndexError, KeyError):
print(«An IndexError or KeyError occurred!»)
1 |
try value=my_dict»d» except(IndexError,KeyError) print(«An IndexError or KeyError occurred!») |
Обратите внимание на то, что в данном примере мы помещаем ошибки, которые мы хотим выявить, внутри круглых скобок. Проблема данного метода в том, что трудно сказать какая именно ошибка произошла, так что предыдущий пример, мы рекомендуем больше чем этот
Зачастую, когда происходит ошибка, вам нужно уведомить пользователя, при помощи сообщения.
В зависимости от сложности данной ошибки, вам может понадобиться выйти из программы. Иногда вам может понадобиться выполнить очистку, перед выходом из программы. Например, если вы открыли соединение с базой данных, вам нужно будет закрыть его, перед выходом из программы, или вы можете закончить с открытым соединением. Другой пример – закрытие дескриптора файла, к которому вы обращаетесь. Теперь нам нужно научиться убирать за собой. Это очень просто, если использовать оператор finally.
11.7. Tools for Working with Lists¶
Many data structure needs can be met with the built-in list type. However,
sometimes there is a need for alternative implementations with different
performance trade-offs.
The module provides an object that is like
a list that stores only homogeneous data and stores it more compactly. The
following example shows an array of numbers stored as two byte unsigned binary
numbers (typecode ) rather than the usual 16 bytes per entry for regular
lists of Python int objects:
>>> from array import array >>> a = array('H', 4000, 10, 700, 22222]) >>> sum(a) 26932 >>> a13 array('H', )
The module provides a object
that is like a list with faster appends and pops from the left side but slower
lookups in the middle. These objects are well suited for implementing queues
and breadth first tree searches:
>>> from collections import deque >>> d = deque() >>> d.append("task4") >>> print("Handling", d.popleft()) Handling task1
unsearched = deque() def breadth_first_search(unsearched): node = unsearched.popleft() for m in gen_moves(node): if is_goal(m): return m unsearched.append(m)
In addition to alternative list implementations, the library also offers other
tools such as the module with functions for manipulating sorted
lists:
>>> import bisect >>> scores = >>> bisect.insort(scores, (300, 'ruby')) >>> scores
The module provides functions for implementing heaps based on
regular lists. The lowest valued entry is always kept at position zero. This
is useful for applications which repeatedly access the smallest element but do
not want to run a full list sort:
Daemon vs. Non-Daemon Threads¶
Up to this point, the example programs have implicitly waited to exit
until all threads have completed their work. Sometimes programs spawn
a thread as a daemon that runs without blocking the main program
from exiting. Using daemon threads is useful for services where there
may not be an easy way to interrupt the thread, or where letting the
thread die in the middle of its work does not lose or corrupt data
(for example, a thread that generates “heart beats” for a service
monitoring tool). To mark a thread as a daemon, pass
when constructing it or call its method with
. The default is for threads to not be daemons.
threading_daemon.py
import threading import time import logging def daemon(): logging.debug('Starting') time.sleep(0.2) logging.debug('Exiting') def non_daemon(): logging.debug('Starting') logging.debug('Exiting') logging.basicConfig( level=logging.DEBUG, format='(%(threadName)-10s) %(message)s', ) d = threading.Thread(name='daemon', target=daemon, daemon=True) t = threading.Thread(name='non-daemon', target=non_daemon) d.start() t.start()
The output does not include the message from the daemon
thread, since all of the non-daemon threads (including the main
thread) exit before the daemon thread wakes up from the
call.
$ python3 threading_daemon.py (daemon ) Starting (non-daemon) Starting (non-daemon) Exiting
To wait until a daemon thread has completed its work, use the
method.
threading_daemon_join.py
import threading import time import logging def daemon(): logging.debug('Starting') time.sleep(0.2) logging.debug('Exiting') def non_daemon(): logging.debug('Starting') logging.debug('Exiting') logging.basicConfig( level=logging.DEBUG, format='(%(threadName)-10s) %(message)s', ) d = threading.Thread(name='daemon', target=daemon, daemon=True) t = threading.Thread(name='non-daemon', target=non_daemon) d.start() t.start() d.join() t.join()
Waiting for the daemon thread to exit using means it
has a chance to produce its message.
$ python3 threading_daemon_join.py (daemon ) Starting (non-daemon) Starting (non-daemon) Exiting (daemon ) Exiting
By default, blocks indefinitely. It is also possible to
pass a float value representing the number of seconds to wait for the
thread to become inactive. If the thread does not complete within the
timeout period, returns anyway.
threading_daemon_join_timeout.py
import threading import time import logging def daemon(): logging.debug('Starting') time.sleep(0.2) logging.debug('Exiting') def non_daemon(): logging.debug('Starting') logging.debug('Exiting') logging.basicConfig( level=logging.DEBUG, format='(%(threadName)-10s) %(message)s', ) d = threading.Thread(name='daemon', target=daemon, daemon=True) t = threading.Thread(name='non-daemon', target=non_daemon) d.start() t.start() d.join(0.1) print('d.isAlive()', d.isAlive()) t.join()
Since the timeout passed is less than the amount of time the daemon
thread sleeps, the thread is still “alive” after
returns.
High-level Module Interface¶
-
An int containing the default buffer size used by the module’s buffered I/O
classes. uses the file’s blksize (as obtained by
) if possible.
- (file, mode=’r’, buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
-
This is an alias for the builtin function.
This function raises an with
arguments , and . The and
arguments may have been modified or inferred from the original call.
- (path)
-
Opens the provided file with mode . This function should be used
when the intent is to treat the contents as executable code.should be a and an absolute path.
The behavior of this function may be overridden by an earlier call to the
. However, assuming that is a
and an absolute path, should always behave
the same as . Overriding the behavior is intended for
additional validation or preprocessing of the file.New in version 3.8.
- exception
-
This is a compatibility alias for the builtin
exception.
- exception
-
An exception inheriting and that is raised
when an unsupported operation is called on a stream.
Как работают процессы
В питоне есть стандартный модуль subprocess, который упрощает управление другими программами, передавая им опции командной строки и организуя обмен данными через каналы (pipe). Мы рассмотрим пример, в котором пользователь запускает программу из командной строки, которая в свою очередь запустит несколько дочерних программ. В данном примере два скрипта – рarent.py и child.py. Запускается parent.py. Child.py выступает в роли аргумента command, который передается в запускаемый процесс. У этого процесса есть стандартный вход, куда мы передаем два аргумента – поисковое слово и имя файла. Мы запустим два экземпляра программы child.py, каждый экземпляр будет искать слово word в своем файле – это будут файлы исходников самих программ. Запись на стандартный вход осуществляет модуль subprocess. Каждый процесс пишет результат своего поиска в консоль. В главном процессе мы ждем, пока все child не закончат свою работу.
Код parent.py:
import os import subprocess import sys child = os.path.join(os.path.dirname(__file__), "./child.py") word = 'word' file = ['./parent.py','./child.py'] pipes = [] for i in range(0,2): command = pipe = subprocess.Popen(command, stdin=subprocess.PIPE) pipes.append(pipe) pipe.stdin.write(word.encode("utf8") + b"\n") pipe.stdin.write(file.encode("utf8") + b"\n") pipe.stdin.close() while pipes: pipe = pipes.pop() pipe.wait()
Код child.py:
import sys word = sys.stdin.readline().rstrip() filename = sys.stdin.readline().rstrip() try: with open(filename, "rb") as fh: while True: current = fh.readline() if not current: break if (word in current ): print("find: {0} {1}".format(filename,word)) except : pass
Limiting Concurrent Access to Resources¶
Sometimes it is useful to allow more than one worker access to a
resource at a time, while still limiting the overall number. For
example, a connection pool might support a fixed number of
simultaneous connections, or a network application might support a
fixed number of concurrent downloads. A Semaphore is one way
to manage those connections.
import logging import random import threading import time logging.basicConfig(level=logging.DEBUG, format='%(asctime)s (%(threadName)-2s) %(message)s', ) class ActivePool(object): def __init__(self): super(ActivePool, self).__init__() self.active = [] self.lock = threading.Lock() def makeActive(self, name): with self.lock self.active.append(name) logging.debug('Running: %s', self.active) def makeInactive(self, name): with self.lock self.active.remove(name) logging.debug('Running: %s', self.active) def worker(s, pool): logging.debug('Waiting to join the pool') with s name = threading.currentThread().getName() pool.makeActive(name) time.sleep(0.1) pool.makeInactive(name) pool = ActivePool() s = threading.Semaphore(2) for i in range(4): t = threading.Thread(target=worker, name=str(i), args=(s, pool)) t.start()
In this example, the ActivePool class simply serves as a
convenient way to track which threads are able to run at a given
moment. A real resource pool would allocate a connection or some other
value to the newly active thread, and reclaim the value when the
thread is done. Here it is just used to hold the names of the active
threads to show that only five are running concurrently.
Applications
- Introduction into the sys module
- Python and the Shell
- Forks and Forking in Python
- Introduction into Threads
- Pipe, Pipes and «99 Bottles of Beer»
- Python Network Scanner
- Graph Theory and Graphs in Python
- Graphs: PyGraph
- Graphs
- A Python Class for Polynomial Functions
- Finite State Machine in Python
- Turing Machine in Python
- Levenshtein Distance
- Verbalize Time in Turkish
- Example for recursive Programming: Towers of Hanoi
- Mastermind / Bulls and Cows
- Creating dynamic websites with WSGI
- Dynamic websites with mod_python
- Dynamic websites with Pylons
- Python, SQL, MySQL and SQLite
- Python Scores
Объект Condition
Объект condition это более продвинутая версия объекта event. Он представляет собой своего рода измененное состояние в приложении, и поток может дожидаться заданных условий, или сигнал о том, что условие было задано. Вот простой пример потребитель/производитель. Для начала, вам нужен объект condition:
Python
# представим добавление нового товара
condition = threading.Condition()
1 |
# представим добавление нового товара condition=threading.Condition() |
Поток «Производитель» должен получить условие, прежде чем он сможет уведомить потребителя о наличии нового товара:
Python
# Поток производителя
… генерация товара
condition.acquire()
… добавление товара в ресурс
condition.notify() # отправляем уведомление о новом товаре
condition.release()
1 |
# Поток производителя …генерациятовара condition.acquire()
…добавлениетоваравресурс condition.notify()# отправляем уведомление о новом товаре condition.release() |
Потребитель должен получить условие (следовательно, и соответствующий замок), после чего может попытаться извлечь товар из ресурсов:
Python
# Поток потребителя
condition.acquire()
while True:
… получаем товар из ресурсов
if item:
break
condition.wait() # в противном случае ожидаем поступление товара
condition.release()
… обработка товара
1 |
# Поток потребителя condition.acquire() whileTrue …получаемтоваризресурсов ifitem break condition.wait()# в противном случае ожидаем поступление товара condition.release() …обработкатовара |
Метод wait освобождает замок, блокирует настоящий поток, пока другой поток вызывает notify или notifyAll на тех же условиях, после чего замок восстанавливается. Если несколько потоков ожидает, метод notify активирует один из потоков, в то время как notifyAll активирует все потоки. Обход блока в методе wait возможен при помощи передачи значения timeout, в виде числа с плавающей запятой в секундах. После этого, метод выдаст результат после указанного времени, даже если мы не вызывали notify. Если вы используете timeout, вам нужно проверить ресурс, чтобы увидеть, произошло ли что-нибудь
Обратите внимание на то, объект condition связан с замком, и этот замок должен удерживаться, пред тем как вы сможете получить доступ к объекту condition. Соответственно, этот замок должен быть освобожден, когда вы выполнили все, что связанно с доступом к condition
В производственном коде вы должны использовать try-finally или with, как показано выше. Для того, чтобы связать condition с существующим замком, передайте замок конструктору Condition. Это также полезно, если вам нужно несколько объектов condition в одном ресурсе:
Python
lock = threading.RLock()
condition_1 = threading.Condition(lock)
condition_2 = threading.Condition(lock)
1 |
lock=threading.RLock() condition_1=threading.Condition(lock) condition_2=threading.Condition(lock) |
Вот и все! Вопросы будут у меня их и у самого есть, но мы справимся. Надеюсь.
The Threading Module
The newer threading module included with Python 2.4 provides much more powerful, high-level support for threads than the thread module discussed in the previous section.
The threading module exposes all the methods of the thread module and provides some additional methods −
-
threading.activeCount() − Returns the number of thread objects that are active.
-
threading.currentThread() − Returns the number of thread objects in the caller’s thread control.
-
threading.enumerate() − Returns a list of all thread objects that are currently active.
In addition to the methods, the threading module has the Thread class that implements threading. The methods provided by the Thread class are as follows −
-
run() − The run() method is the entry point for a thread.
-
start() − The start() method starts a thread by calling the run method.
-
join() − The join() waits for threads to terminate.
-
isAlive() − The isAlive() method checks whether a thread is still executing.
-
getName() − The getName() method returns the name of a thread.
-
setName() − The setName() method sets the name of a thread.
История 1: Task.Delay & TimerQueue
Polling:Long polling:
- Меньший объем трафика
- Клиент узнает о результате быстрее
pollinglong polling
1.3 Lock convoy
- Много потоков пытаются захватить один lock
- Под lock’ом выполняется мало кода
- Время тратится на синхронизацию потоков, а не на выполнение кода
- Блокируются потоки из тредпула – они не бесконечны
1.4 TimerQueue
- Управляет таймерами в .NET-приложении.
- Таймеры используются в:
— Task.Delay
— CancellationTocken.CancelAfter
— HttpClient
- Global state (per-appdomain):
— Double linked list of TimerQueueTimer
— Lock object - Routine, запускающая коллбэки таймеров
- Таймеры не упорядочены по времени срабатывания
- Добавление таймера: O(1) + lock
- Удаление таймера: O(1) + lock
- Запуск таймеров: O(N) + lock
https://github.com/Microsoft/dotnet-framework-early-access/blob/master/release-notes/NET48/dotnet-48-changes.mdhttps://github.com/dotnet/coreclr/labels/netfx-port-consider
1.6 Task.Delay: выводы
- Подводные камни везде — даже в самых используемых вещах
- Проводите нагрузочное тестирование
- Переходите на Core, получайте багфиксы (и новые баги) первыми 🙂
Subclassing Thread¶
At start-up, a does some basic initialization and then
calls its method, which calls the target function passed
to the constructor. To create a subclass of , override
to do whatever is necessary.
threading_subclass.py
import threading import logging class MyThread(threading.Thread): def run(self): logging.debug('running') logging.basicConfig( level=logging.DEBUG, format='(%(threadName)-10s) %(message)s', ) for i in range(5): t = MyThread() t.start()
The return value of is ignored.
$ python3 threading_subclass.py (Thread-1 ) running (Thread-2 ) running (Thread-3 ) running (Thread-4 ) running (Thread-5 ) running
Because the and values passed to the
constructor are saved in private variables using names prefixed with
, they are not easily accessed from a subclass. To pass
arguments to a custom thread type, redefine the constructor to save
the values in an instance attribute that can be seen in the subclass.
threading_subclass_args.py
import threading import logging class MyThreadWithArgs(threading.Thread): def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None): super().__init__(group=group, target=target, name=name, daemon=daemon) self.args = args self.kwargs = kwargs def run(self): logging.debug('running with %s and %s', self.args, self.kwargs) logging.basicConfig( level=logging.DEBUG, format='(%(threadName)-10s) %(message)s', ) for i in range(5): t = MyThreadWithArgs(args=(i,), kwargs={'a' 'A', 'b' 'B'}) t.start()
uses the same API as , but
another class could easily change the constructor method to take more
or different arguments more directly related to the purpose of the
thread, as with any other class.
Использование потоков
Мы начнем с простого примера, который демонстрирует работу потоков. Мы наследуем класс Thread в класс MyThread и укажем, чтобы его имя выводилось как stdout. Попробуем!
Python
# -*- coding: utf-8 -*-
import random
import time
from threading import Thread
class MyThread(Thread):
«»»
A threading example
«»»
def __init__(self, name):
«»»Инициализация потока»»»
Thread.__init__(self)
self.name = name
def run(self):
«»»Запуск потока»»»
amount = random.randint(3, 15)
time.sleep(amount)
msg = «%s is running» % self.name
print(msg)
def create_threads():
«»»
Создаем группу потоков
«»»
for i in range(5):
name = «Thread #%s» % (i+1)
my_thread = MyThread(name)
my_thread.start()
if __name__ == «__main__»:
create_threads()
1 |
# -*- coding: utf-8 -*- importrandom importtime fromthreadingimportThread classMyThread(Thread) «»» A threading example def__init__(self,name) «»»Инициализация потока»»» Thread.__init__(self) self.name=name defrun(self) «»»Запуск потока»»» amount=random.randint(3,15) time.sleep(amount) msg=»%s is running»%self.name print(msg) defcreate_threads() «»» Создаем группу потоков foriinrange(5) name=»Thread #%s»%(i+1) my_thread=MyThread(name) my_thread.start() if__name__==»__main__» create_threads() |
В этом коде мы импортировали модули random и time, также мы импортировали класс Thread из модуля threading Python. Далее, мы наследуем класс Thread, и переопределили его метод __init__ для принятия аргумента, под названием name. Для начала потока, вам нужно вызывать метод start().
После запуска потока, он автоматически вызовет метод run. Мы переопределили метод run таким образом, чтобы он выбирал случайный отсчет времени для «сна». Пример random.randint указывает Python выбрать случайное число от 3 до 15. После этого мы указываем потоку «спать» столько секунд, сколько было выбрано случайным способом, для симуляции его настоящей работы. Далее мы ввели имя потока, чтобы сказать пользователю, что он закончился. Функция create_threads создаст 5 потоков, дав каждому из них уникальное имя. Если вы запустите данный код, вы увидите что-то вроде этого:
Python
Thread #2 is running
Thread #3 is running
Thread #1 is running
Thread #4 is running
Thread #5 is running
1 |
Thread #2 is running Thread #3 is running Thread #1 is running Thread #4 is running Thread #5 is running |
Порядок выхода каждый раз будет разным. Попробуйте запустить код несколько раз, чтобы увидеть смену порядка. Теперь давайте напишем что-нибудь более практичное!
11.4. Multi-threading¶
Threading is a technique for decoupling tasks which are not sequentially
dependent. Threads can be used to improve the responsiveness of applications
that accept user input while other tasks run in the background. A related use
case is running I/O in parallel with computations in another thread.
The following code shows how the high level module can run
tasks in background while the main program continues to run:
import threading, zipfile class AsyncZip(threading.Thread): def __init__(self, infile, outfile): threading.Thread.__init__(self) self.infile = infile self.outfile = outfile def run(self): f = zipfile.ZipFile(self.outfile, 'w', zipfile.ZIP_DEFLATED) f.write(self.infile) f.close() print('Finished background zip of:', self.infile) background = AsyncZip('mydata.txt', 'myarchive.zip') background.start() print('The main program continues to run in foreground.') background.join() # Wait for the background task to finish print('Main program waited until background was done.')
The principal challenge of multi-threaded applications is coordinating threads
that share data or other resources. To that end, the threading module provides
a number of synchronization primitives including locks, events, condition
variables, and semaphores.