Как сделать программу многопоточной

Когда-то давно мы делали простой таймер с напоминанием на Python . Он работал так:

  1. Мы спрашивали пользователя, о чём ему напомнить и через сколько минут.
  2. Программа на это время засыпала и ничего не делала.
  3. Как только время сна заканчивалось, программа просыпалась и выводила напоминание.

У такой схемы есть минус: мы не можем пользоваться программой и выделенными на неё ресурсами до тех пор, пока она не проснётся. Процессор по кругу гоняет пустые команды и ждёт, когда можно будет продолжить полезную работу. Чтобы процессор и программа могли во время работы таймера делать что-то ещё, используют потоки.

Что такое поток

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

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

МНОГОПОТОЧНОСТЬ НА PYTHON | МОДУЛЬ THREADING

Важно понимать, что поток — это высокоуровневое понятие из области программирования. На уровне вашего «железа» эти потоки всё ещё могут обсчитываться последовательно. Но благодаря тому, что они будут обсчитываться быстро, вам может показаться, что они работают параллельно.

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

Представим такую ситуацию:

  • У вас на руке смарт-часы, которые собирают данные о вашем пульсе, УФ-излучении и движениях. На смарт-часах работает программа, которая обрабатывает эти данные.
  • Программа состоит из четырёх функций. Первая собирает данные с датчиков. Три другие обрабатывают эти данные и делают выводы.
  • Пока первая функция не собрала нужные данные, ничего другого не происходит.
  • Как только данные введены, запускаются три оставшиеся функции. Они не зависят друг от друга и каждая считает своё.
  • Как только все три функции закончат работу, программа выдаёт нужный результат.

А теперь давайте посмотрим, как это выглядит в однопоточной и многопоточной системе. Видно, что если процессор позволяет делать несколько дел одновременно, в многопоточном режиме программа будет работать быстрее:

Источник: dzen.ru

№34 Потоки и многопоточность / для начинающих

Модуль threading в Python используется для реализации многопоточности в программах. В этом материале разберемся с Thread и разными функциями этого модуля.

Что такое поток?

В информатике поток — это минимальная единица работы, запланированная для выполнения операционной системой.

О потоках нужно знать следующее:

  • Они существуют внутри процесса;
  • В одном процессе может быть несколько потоков;
  • Потоки в одном процессе разделяют состояние и память родительского процесса.

Модуль threading в Python можно представить таким простым примером:

Многопоточность | Потоки | thread | Многопоточное программирование | Уроки | C++ #1



import time
from threading import Thread

def sleepMe(i):
print(«Поток %i засыпает на 5 секунд.n» % i)
time.sleep(5)
print(«Поток %i сейчас проснулся.n» % i)

for i in range(10):
th = Thread(target=sleepMe, args=(i, ))
th.start()

После запуска скрипта вывод будет следующий:

Поток 0 засыпает на 5 секунд. Поток 3 засыпает на 5 секунд. Поток 1 засыпает на 5 секунд. Поток 4 засыпает на 5 секунд. Поток 2 засыпает на 5 секунд. Поток 5 засыпает на 5 секунд. Поток 6 засыпает на 5 секунд. Поток 7 засыпает на 5 секунд. Поток 8 засыпает на 5 секунд.

Читайте также:
Как войти в программу Microsoft word

Поток 9 засыпает на 5 секунд. Поток 0 сейчас проснулся. Поток 3 сейчас проснулся. Поток 1 сейчас проснулся. Поток 4 сейчас проснулся. Поток 2 сейчас проснулся. Поток 5 сейчас проснулся.

Поток 6 сейчас проснулся. Поток 7 сейчас проснулся. Поток 8 сейчас проснулся. Поток 9 сейчас проснулся.

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

Функции threading в Python

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

threading.active_count()

Эта функция возвращает количество исполняемых на текущий момент потоков. Изменим последнюю программу, чтобы она выглядела вот так:

import time
import threading
from threading import Thread

def sleepMe(i):
print(«Поток %i засыпает на 5 секунд.» % i)
time.sleep(5)
print(«Поток %i сейчас проснулся.» % i)

for i in range(10):
th = Thread(target=sleepMe, args=(i, ))
th.start()
print(«Запущено потоков: %i.» % threading.active_count())

Теперь в выводе будет показываться количество активных на текущий момент потоков:

Поток 0 засыпает на 5 секунд.Запущено потоков: 3. Запущено потоков: 4.Поток 1 засыпает на 5 секунд. Запущено потоков: 5.Поток 2 засыпает на 5 секунд. Поток 3 засыпает на 5 секунд.Запущено потоков: 6. Запущено потоков: 7.Поток 4 засыпает на 5 секунд. Поток 5 засыпает на 5 секунд.Запущено потоков: 8. Поток 6 засыпает на 5 секунд.Запущено потоков: 9. Запущено потоков: 10.Поток 7 засыпает на 5 секунд.

Поток 8 засыпает на 5 секунд.Запущено потоков: 11. Поток 9 засыпает на 5 секунд.Запущено потоков: 12. Поток 0 сейчас проснулся. Поток 1 сейчас проснулся. Поток 2 сейчас проснулся. Поток 3 сейчас проснулся. Поток 4 сейчас проснулся. Поток 5 сейчас проснулся. Поток 6 сейчас проснулся. Поток 7 сейчас проснулся.

Поток 8 сейчас проснулся. Поток 9 сейчас проснулся.

Также обратите внимание, что после запуска всех потоков счетчик показывает число 11, а не 10. Причина в том, что основной поток также учитывается наравне с 10 остальными.

threading.current_thread()

Эта функция возвращает исполняемый прямо сейчас поток. С ее помощью можно выполнять определенные действия с ним. Поменяем все тот же скрипт:

Источник: pythonru.com

Многопоточное программирование в Java 8. Часть первая. Параллельное выполнение кода с помощью потоков

Обложка: Многопоточное программирование в Java 8. Часть первая. Параллельное выполнение кода с помощью потоков

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

Впервые Concurrency API был представлен вместе с выходом Java 5 и с тех пор постоянно развивался с каждой новой версией Java. Большую часть примеров можно реализовать на более старых версиях, однако в этой статье я собираюсь использовать лямбда-выражения. Если вы все еще не знакомы с нововведениями Java 8, рекомендую посмотреть мое руководство.

Потоки и задачи

Все современные операционные системы поддерживают параллельное выполнение кода с помощью процессов и потоков. Процесс — это экземпляр программы, который запускается независимо от остальных. Например, когда вы запускаете программу на Java, ОС создает новый процесс, который работает параллельно другим. Внутри процессов мы можем использовать потоки, тем самым выжав из процессора максимум возможностей.

Потоки (threads) в Java поддерживаются начиная с JDK 1.0. Прежде чем запустить поток, ему надо предоставить участок кода, который обычно называется «задачей» (task). Это делается через реализацию интерфейса Runnable , у которого есть только один метод без аргументов, возвращающий void — run() . Вот пример того, как это работает:

Runnable task = () -> < String threadName = Thread.currentThread().getName(); System.out.println(«Hello » + threadName); >; task.run(); Thread thread = new Thread(task); thread.start(); System.out.println(«Done!»);

Поскольку интерфейс Runnable функциональный, мы можем использовать лямбда-выражения, которые появились в Java 8. В примере мы создаем задачу, которая выводит имя текущего потока на консоль, и запускаем ее сначала в главном потоке, а затем — в отдельном.

Читайте также:
Как называлась программа Северного общества подготовленная н м муравьевым

Результат выполнения этого кода может выглядеть так:

Hello main Hello Thread-0 Done!
Hello main Done! Hello Thread-0

Из-за параллельного выполнения мы не можем сказать, будет наш поток запущен до или после вывода «Done!» на экран. Эта особенность делает параллельное программирование сложной задачей в больших приложениях.

Java IT Lead в проект Сфера Иннотех , , можно удалённо , По итогам собеседования

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

Runnable runnable = () -> < try < String name = Thread.currentThread().getName(); System.out.println(«Foo » + name); TimeUnit.SECONDS.sleep(1); System.out.println(«Bar » + name); >catch (InterruptedException e) < e.printStackTrace(); >>; Thread thread = new Thread(runnable); thread.start();

Когда вы запустите этот код, вы увидите секундную задержку между выводом первой и второй строки на экран. TimeUnit — полезный класс для работы с единицами времени, но то же самое можно сделать с помощью Thread.sleep(1000) .

Работать с потоками напрямую неудобно и чревато ошибками. Поэтому в 2004 году в Java 5 добавили Concurrency API. Он находится в пакете java.util.concurrent и содержит большое количество полезных классов и методов для многопоточного программирования. С тех пор Concurrency API непрерывно развивался и развивается.

Давайте теперь подробнее рассмотрим одну из самых важных частей Concurrency API — сервис исполнителей (executor services).

Исполнители

Concurrency API вводит понятие сервиса-исполнителя (ExecutorService) — высокоуровневую замену работе с потоками напрямую. Исполнители выполняют задачи асинхронно и обычно используют пул потоков, так что нам не надо создавать их вручную. Все потоки из пула будут использованы повторно после выполнения задачи, а значит, мы можем создать в приложении столько задач, сколько хотим, используя один исполнитель.

Вот как будет выглядеть наш первый пример с использованием исполнителя:

ExecutorService executor = Executors.newSingleThreadExecutor(); executor.submit(() -> < String threadName = Thread.currentThread().getName(); System.out.println(«Hello » + threadName); >); // => Hello pool-1-thread-1

Класс Executors предоставляет удобные методы-фабрики для создания различных сервисов исполнителей. В данном случае мы использовали исполнитель с одним потоком.

Результат выглядит так же, как в прошлый раз. Но у этого кода есть важное отличие — он никогда не остановится. Работу исполнителей надо завершать явно. Для этого в интерфейсе ExecutorService есть два метода: shutdown() , который ждет завершения запущенных задач, и shutdownNow() , который останавливает исполнитель немедленно.

Вот как я предпочитаю останавливать исполнителей:

try < System.out.println(«attempt to shutdown executor»); executor.shutdown(); executor.awaitTermination(5, TimeUnit.SECONDS); >catch (InterruptedException e) < System.err.println(«tasks interrupted»); >finally < if (!executor.isTerminated()) < System.err.println(«cancel non-finished tasks»); >executor.shutdownNow(); System.out.println(«shutdown finished»); >

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

Callable и Future

Кроме Runnable , исполнители могут принимать другой вид задач, который называется Callable . Callable — это также функциональный интерфейс, но, в отличие от Runnable , он может возвращать значение.

Давайте напишем задачу, которая возвращает целое число после секундной паузы:

Callable task = () -> < try < TimeUnit.SECONDS.sleep(1); return 123; >catch (InterruptedException e) < throw new IllegalStateException(«task interrupted», e); >>;

Callable-задачи также могут быть переданы исполнителям. Но как тогда получить результат, который они возвращают? Поскольку метод submit() не ждет завершения задачи, исполнитель не может вернуть результат задачи напрямую. Вместо этого исполнитель возвращает специальный объект Future, у которого мы сможем запросить результат задачи.

ExecutorService executor = Executors.newFixedThreadPool(1); Future future = executor.submit(task); System.out.println(«future done? » + future.isDone()); Integer result = future.get(); System.out.println(«future done? » + future.isDone()); System.out.print(«result: » + result);

Читайте также:
Как найти программы паразиты на компьютере

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

Вызов метода get() блокирует поток и ждет завершения задачи, а затем возвращает результат ее выполнения. Теперь future.isDone() вернет true , и мы увидим на консоли следующее:

future done? false future done? true result: 123

Задачи жестко связаны с сервисом исполнителей, и, если вы его остановите, попытка получить результат задачи выбросит исключение:

executor.shutdownNow(); future.get();

Вы, возможно, заметили, что на этот раз мы создаем сервис немного по-другому: с помощью метода newFixedThreadPool(1) , который вернет исполнителя с пулом в один поток. Это эквивалентно вызову метода newSingleThreadExecutor() , однако мы можем изменить количество потоков в пуле.

Таймауты

Любой вызов метода future.get() блокирует поток до тех пор, пока задача не будет завершена. В наихудшем случае выполнение задачи не завершится никогда, блокируя ваше приложение. Избежать этого можно, передав таймаут:

ExecutorService executor = Executors.newFixedThreadPool(1); Future future = executor.submit(() -> < try < TimeUnit.SECONDS.sleep(2); return 123; >catch (InterruptedException e) < throw new IllegalStateException(«task interrupted», e); >>); future.get(1, TimeUnit.SECONDS);

Выполнение этого кода вызовет TimeoutException :

Exception in thread «main» java.util.concurrent.TimeoutException at java.util.concurrent.FutureTask.get(FutureTask.java:205)

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

InvokeAll

Исполнители могут принимать список задач на выполнение с помощью метода invokeAll() , который принимает коллекцию callable-задач и возвращает список из Future .

ExecutorService executor = Executors.newWorkStealingPool(); List> callables = Arrays.asList( () -> «task1», () -> «task2», () -> «task3»); executor.invokeAll(callables) .stream() .map(future -> < try < return future.get(); >catch (Exception e) < throw new IllegalStateException(e); >>) .forEach(System.out::println);

В этом примере мы использовали функциональные потоки Java 8 для обработки задач, возвращенных методом invokeAll . Мы прошлись по всем задачам и вывели их результат на консоль. Если вы не знакомы с потоками (streams) Java 8, смотрите мое руководство.

InvokeAny

Другой способ отдать на выполнение несколько задач — метод invokeAny() . Он работает немного по-другому: вместо возврата Future он блокирует поток до того, как завершится хоть одна задача, и возвращает ее результат.

Чтобы показать, как работает этот метод, создадим метод, эмулирующий поведение различных задач. Он будет возвращать Callable , который вернет указанную строку после необходимой задержки:

Callable callable(String result, long sleepSeconds) < return () ->< TimeUnit.SECONDS.sleep(sleepSeconds); return result; >; >

Используем этот метод, чтобы создать несколько задач с разными строками и задержками от одной до трех секунд. Отправка этих задач исполнителю через метод invokeAny() вернет результат задачи с наименьшей задержкой. В данном случае это «task2»:

ExecutorService executor = Executors.newWorkStealingPool(); List> callables = Arrays.asList( callable(«task1», 2), callable(«task2», 1), callable(«task3», 3)); String result = executor.invokeAny(callables); System.out.println(result); // => task2

В примере выше использован еще один вид исполнителей, который создается с помощью метода newWorkStealingPool() . Этот метод появился в Java 8 и ведет себя не так, как другие: вместо использования фиксированного количества потоков он создает ForkJoinPool с определенным параллелизмом (parallelism size), по умолчанию равным количеству ядер машины.

ForkJoinPool впервые появился в Java 7, и мы рассмотрим его подробнее в следующих частях нашего руководства. А теперь давайте посмотрим на исполнители с планировщиком (scheduled executors).

Исполнители с планировщиком

Рейтинг
( Пока оценок нет )
Загрузка ...
EFT-Soft.ru