- Как запустить два процесса одновременно linux
- 6.2. Использование getpid() и getppid()
- 6.3. Порождение процесса
- 6.4. Замена образа процесса
- Распараллеливание задач в Linux
- Первый способ: распараллеливание средствами mencoder
- Второй способ: распараллеливание средствами Bash
- Третий способ: распараллеливание средствами GNU make
- MNorin.com
- Блог про Linux, Bash и другие информационные технологии
- Параллельное выполнение в bash
- Использование фонового режима
- Использование пайпа
- Параллельное выполнение и ограничение количества фоновых задач в скрипте
Как запустить два процесса одновременно linux
Эта глава открывает большую и очень важную для Linux-программиста тему многозадачности. Описать все сразу не получится, поэтому мы будем неоднократно возвращаться к многозадачности в последующих главах книги. Пристегните ремни покрепче!
Наберите в своей оболочке следующую команду:
На экран будут выведен список всех работающих в системе процессов. Если хотите посчитать количество процессов, наберите что-нибудь, набодобие этого:
Первое число — это количество работающих в системе процессов. Пользователи KDE могут воспользоваться программой kpm, а пользователи Gnome — программой gnome-system-monitor для получения информации о процессах. На то он и Linux, чтобы позволять пользователю делать одно и то же разными способами.
Возникает вопрос: «Что такое процесс?». Процессы в Linux, как и файлы, являются аксиоматическими понятиями. Иногда процесс отождествляют с запущенной программой, однако это не всегда так. Будем считать, что процесс — это рабочая единица системы, которая выполняет что-то. Многозадачность — это возможность одновременного сосуществования нескольких процессов в одной системе.
Linux — многозадачная операционная система. Это означает что процессы в ней работают одновременно. Естественно, это условная формулировка. Ядро Linux постоянно переключает процессы, то есть время от времени дает каждому из них сколько-нибудь процессорного времени. Переключение происходит довольно быстро, поэтому нам кажется, что процессы работают одновременно.
Одни процессы могут порождать другие процессы, образовывая древовидную структуру. Порождающие процессы называются родителями или родительскими процессами, а порожденные — потомками или дочерними процессами. На вершине этого «дерева» находится процесс init, который порождается автоматически ядром в процесссе загрузки системы.
К каждому процессу в системе привязана пара целых неотрицательных чисел: идентификатор процесса PID (Process IDentifier) и идентификатор родительского процесса PPID (Parent Process IDentifier). Для каждого процесса PID является уникальным (в конкретный момент времени), а PPID равен идентификатору процесса-родителя. Если ввести в оболочку команду ps -ef, то на экран будет выведен список процессов со значениями их PID и PPID (вторая и третья колонки соотв.). Вот, например, что творится у меня в системе:
Надо отметить, что процесс init всегда имеет идентификатор 1 и PPID равный 0. Хотя в реальности процесса с идентификатором 0 не существует. Дерево процессов можно также пресставить в наглядном виде при помощи опции —forest программы ps:
Если вызвать программу ps без аргументов, то будет выведен список процессов, принадлежащих текущей группе, то есть работающих под текущим терминалом. О том, что такое терминалы и группы процессов, будет рассказано в последующих главах.
6.2. Использование getpid() и getppid()
Процесс может узнать свой идентификатор (PID), а также родительский идентификатор (PPID) при помощи системных вызовов getpid() и getppid().
Системные вызовы getpid() и getppid() имеют следующие прототипы:
Для использования getpid() и getppid() в программу должны быть включены директивой #include заголовочные файлы unistd.h и sys/types.h (для типа pid_t). Вызов getpid() возвращает идентификатор текущего процесса (PID), а getppid() возвращает идентификатор родителя (PPID). pid_t — это целый тип, размерность которого зависит от конкретной системы. Значениями этого типа можно оперировать как обычными целыми числами типа int.
Рассмотрим теперь простую программу, которая выводит на экран PID и PPID, а затем «замирает» до тех пор, пока пользователь не нажмет .
Проверим теперь, как работает эта программа. Для этого откомпилируем и запустим ее:
Теперь, не нажимая , откроем другое терминальное окно и проверим, правильность работы системных вызовов getpid() и getppid():
6.3. Порождение процесса
Как уже говорилось ранее, процесс в Linux — это нечто, выполняющее программный код. Этот код называют образом процесса (process image). Рассмотрим простой пример, когда вы находитесь в оболочке bash и выполняете команду ls. В этом случае происходит следующее. Образ программы-оболочки bash выполняется в процессе #1. Затем вы вводите команду ls, и оболочка определяет, что нужно запустить внешнюю программу (/bin/ls). Тогда процесс #1 создает свою почти точную копию, процесс #2, который выполняет тот же самый программный код. После этого процесс #2 заменяет свой текущий образ (оболочку) другим образом (программой /bin/ls). В итоге получаем отдельный процесс, выполняющий отдельную программу.
«К чему такая путаница?» — спросите вы. Зачем сначала «клонировать» процесс, а затем заменять в нем образ? Не проще ли все делать одной-единственной операцией? Ответы на подобные вопросы дать тяжело, но, как правило, с опытом прихоидит понимание того, что подобная схема является одной из граней красоты Unix-систем.
Попробуем все-таки разобраться, почему в Unix-системах порождение процесса отделено от запуска программы. Для этого выясним, что же происходит с данными при «клонировании» процесса. Итак, каждый процесс хранит в своей памяти различные данные (переменные, файловые дескрипторы и проч.). При порождении нового процесса, потомок получает точную копию данных родителя. Но как только новый процесс создан, родитель и потомок уже распоряжаются своими копиями по своему усмотрению. Это позволяет распараллелить программу, заставив ее выполнять какой-нибудь трудоемкий алгоритм в отдельном процессе.
Может быть кто-то из вас слышал про то, что в Linux есть потоки, которые позволяют в одной программе реализовывать параллельное выполнение нескольких функций. Опять же возникает вопрос: «Если есть потоки, зачем вся эта головомойка с клонированиями и заменой образов?». А дело в том, что потоки работают с общими данными и выполняются в одной программе. Если в потоке произошло что-то страшное, то это, как правило, отражается на всей программе в целом. Хотя технически потоки реализованы в Linux на базе процессов, но процесс все же является более независимой единицей. Крах дочернего процесса никак не отражается на работе родителя, если сам родитель этого не пожелает.
По правде сказать, программисты редко прибегают к методике распараллеливания одной программы при помощи процессов. Но суть в том, что в Unix-системах программист обладает полной свободой выбора стратегии многозадачности. И это здорово!
Разберемся теперь с тем, как на практике происходит «клонирование» процессов. Для этого используется простой системный вызов fork(), прототип которого находится в файле unistd.h:
Если fork() завершается с ошибкой, то возвращается -1. Это редкий случай, связанный с нехваткой памяти или превышением лимита на количество процессов. Но если разделение произошло, то программе нужно позаботиться об идентификации своего «Я», то есть определении того, где родитель, а где потомок. Это делается очень просто: в родительский процесс fork() возвращает идентификатор потомка, а потомок получает 0. Следующий пример демонстрирует то, как это происходит.
Проверяем, что получилось:
Обратите внимание, что поскольку после вызова fork() программу выполняли уже два независимых процесса, то сообщение родителя вполне могло бы появиться первым.
6.4. Замена образа процесса
Итак, теперь мы умеем порождать процессы. Научимся теперь заменять образ текущего процесса другой программой. Для этих целей используется системный вызов execve(), который объявлен в заголовочном файле unistd.h вот так:
Все очень просто: системный вызов execve() заменяет текущий образ процесса программой из файла с именем path, набором аргументов argv и окружением envp. Здесь следует только учитывать, что path — это не просто имя программы, а путь к ней. Иными словами, чтобы запустить ls, нужно в первом аргументе указать «/bin/ls».
Массивы строк argv и envp обязательно должны заканчиваться элементом NULL. Кроме того, следует помнить, что первый элемент массива argv (argv[0]) — это имя программы или что-либо иное. Непосредственные аргументы программы отсчитываются от элемента с номером 1.
В случае успешного завершения execve() ничего не возвращает, поскольку новая программа получает полное и безвозвратное управление текущим процессом. Если произошла ошибка, то по традиции возвращается -1.
Рассмотрим теперь пример программы, которая заменяет свой образ другой программой.
Итак, данная программа выводит свой PID и передает безвозвратное управление программе cat без аргументов и без окружения. Проверяем:
Программа вывела идентификатор процесса и замерла в смиренном ожидании. Откроем теперь другое терминальное окно и проверим, что же творится с нашим процессом:
Итак, мы убедились, что теперь процесс 30150 выполняет программа cat. Теперь можно вернуться в исходное окно и нажатием Ctrl+D завершить работу cat.
И, наконец, следующий пример демонстрирует запуск программы в отдельном процессе.
Обратите внимание, что поскольку execve() не может возвращать ничего кроме -1, то для обработки возможной ошибки вовсе не обязательно создавать ветвление. Иными словами, если вызов execve() возвратил что-то, то это однозначно ошибка.
Источник
Распараллеливание задач в Linux
Потребовалось мне перекодировать некоторое количество видео-файлов. Для этого я написал следующий сценарий:
recode() <
mencoder -o $2 $1 -ovc x264 -x264encopts bitrate=22000:keyint=50 -oac mp3lame -lameopts vbr=3:br=320 -fps 50
>
recode input/00108.mts 00108.avi
recode input/00109.mts 00109.avi
.
.
Казалось-бы все готово, но я заметил, что загружен только один процессор из двух, а это значит, что этот процесс можно ускорить, раза в два.
Первый способ: распараллеливание средствами mencoder
Можно задать опции mencoder. Для x264 кодека есть возможность указать количество потоков:
- threads=
Использовать потоки для кодирования одновременно на нескольких процессорах (по умолчанию: 1). Это немного ухудшает качество кодирования. 0 или ‘auto’ — автоматически определить количество процессоров и использовать соответствующее количество потоков.
Способ хороший, но не универсальный и возможно ухудшение качества результата.
Второй способ: распараллеливание средствами Bash
Способ прост, запускаем процессы параллельно, например так:
(recode input/00108.mts 00108.avi
recode input/00109.mts 00109.avi
.
. ) &
(recode input/00108.mts 00110.avi
recode input/00109.mts 00111.avi
.
. )
Недостаток метода в том, что из-за того что файлы разного размера, время на их обработку может существенно различаться и в итоге один процессор может разгрузиться значительно раньше другого.
Третий способ: распараллеливание средствами GNU make
Мне хотелось каким-то образом улучшить сценарий, чтобы всегда параллельно работало две задачи и по мере их завершения запускались новые. Размышляя как бы мне это сделать, я вспомнил, что существует замечательная утилита сборки, которая может делать именно то, что мне надо. Получилось следующее:
all: 00108.avi 00109.avi 00110.avi 00111.avi 00118.avi 00119.avi 00120.avi 00123.avi
Получилось на удивление просто и коротко. В начале перечисляется список всех файлов, которые я хочу получить. За ним следует путь до исходных файлов и правило сборки. Запускать командой «make -j 2», чтобы работало одновременно 2 процесса.
Источник
MNorin.com
Блог про Linux, Bash и другие информационные технологии
Параллельное выполнение в bash
В большинстве командных оболочек команды выполняются по умолчанию последовательно. И это, в принципе, нормально. Потому что человек с системой взаимодействует последовательно, обычно нет необходимости несколько команд выполнять параллельно. Bash в этом смысле тоже не исключение. Но при автоматизации возможность параллельного выполнения может быть полезной. Давайте посмотрим, как организовать параллельное выполнение в bash.
Использование фонового режима
Для организации параллельной работы нескольких программ часто используется запуск в фоновом режиме при помощи знака амперсанда — &. Например:
Команда будет работать в фоне, при этом из текущей оболочки можно выполнять команды. Таким образом уже можно распараллелить какие-то действия. Можно запустить сразу несколько команд таким образом и дождаться, пока они все отработают. Для ожидания запущенных дочерних процессов используется команда wait. Эта команда без параметров ожидает окончания работы всех дочерних процессов, соответственно, для ожидания окончания 5 процессов понадобится выполнить команду всего 1 раз. В принципе, это легко реализуется через цикл. Например, так:
И результат работы этого скрипта:
Как видите, с виду одинаковые команды завершились не в том порядке, в котором мы их запустили. Давайте посмотрим теперь общее время выполнения скрипта.
Общее время работы скрипта чуть больше 10 секунд, что доказывает, что наши команды выполнились параллельно, а увеличение времени выполнения говорит о том, что они были запущены не в одно и то же время, были небольшие таймауты между запусками. Но они были очень маленькими, поэтому такой запуск пяти процессов занимает практически такое же время, как запуск одного.
Использование пайпа
При использовании пайпов, которые перенаправляют вывод одной программы на вход другой, процессы выполняются параллельно. Отсюда вытекает еще один способ параллельного выполнения нескольких команд — перенаправить между ними какие-то данные с помощью пайпа. Например:
Тут важно помнить вот что: если вам не нужно передавать реально какие-то данные между программами, то надо предварительно убедиться, что входные данные эти программы получат в виде опций командной строки и не будут использовать те данные, которые были переданы им с помощью пайпа. Хотя здесь, в принципе, возможен такой вариант:
Этот вариант до использования в скриптах, естественно, нужно обязательно проверить в ручном режиме и посмотреть в страницах руководств используемых программ (если там есть такая информация), что имеет больший приоритет — опции командной строки или стандартный поток ввода, потому что некоторые программы могут игнорировать командную строку, если данные передаются через стандартный поток ввода.
Параллельное выполнение и ограничение количества фоновых задач в скрипте
Давайте рассмотрим такую практическую задачу — запустить 100 процессов параллельно, но так, чтобы работало одновременно не более 10 процессов. В общем, достаточно простая задача. Предположим, что все процессы работают произвольное количество времени. Пусть запуск одной задачи будет выглядеть как запуск команды sleep со случайным параметром от 0 до 29. Тогда скрипт будет выглядеть следующим образом:
Смысл этого скрипта в целом такой: ограничиваем максимально число дочерних фоновых процессов так, чтобы их одновременно было не более 10. Как только один процесс заканчивает свою работу, запускаем следующий. И так далее, пока не выполним 100 фоновых задач. Для порядка отслеживаем в скрипте окончание работы дочерних процессов после запуска последних, только потом заканчиваем работу самого скрипта.
Таким простым способом можно ограничить количество одновременно запущенных фоновых задач в скрипте и при этом отслеживать, сколько из них в данный момент работают. Чтобы было более понятно, запустите скрипт и увидите, когда запускается следующие итерации цикла, и когда изменяется количество дочерних процессов.
Источник