Что такое конвейер команд в теории ос
Перейти к содержимому

Что такое конвейер команд в теории ос

  • автор:

Конвейер (Unix) — Pipeline (Unix)

В Unix-подобных компьютерных операционных системах конвейер представляет собой механизм межпроцессного взаимодействия с использованием передачи сообщений. Конвейер — это набор процессов, объединенных в цепочку своими стандартными потоками, так что выходной текст каждого процесса (stdout ) передается непосредственно как вход (stdin ) к следующему. Второй процесс запускается, поскольку первый процесс все еще выполняется, и они выполняются одновременно. Идею конвейеров отстаивал Дуглас Макилрой на прародине Unix, в Bell Labs, во время разработки Unix, формируя его философию инструментария.. Он назван по аналогии с физическим конвейером . Ключевой особенностью этих конвейеров является «сокрытие внутренних компонентов» (Ritchie Thompson, 1974). Это, в свою очередь, обеспечивает большую ясность и простоту системы.

Эта статья посвящена анонимным каналам, где данные, записанные одним процессом, буферизуются операционной системой до тех пор, пока они не будут прочитаны следующим процессом, и этот однонаправленный канал исчезает, когда процессы завершены. Это отличается от именованных каналов, где сообщения передаются в канал или из канала, которому присвоено имя в виде файла, и остаются после завершения процессов. Стандартный синтаксис оболочки оболочки для анонимных каналов состоит в том, чтобы перечислить несколько команд, разделенных вертикальными чертами («каналы» в обычном слове Unix):

process1 | process2 | process3

Например, чтобы вывести список файлов в текущем каталоге (ls ), оставьте только строки вывода ls, содержащие строку «key» (grep ) и просмотрите результат на прокручиваемой странице (меньше ), пользователь вводит следующее в командной строке терминала:

ls -l | ключ grep | less

» ls -l «создает процесс, вывод (stdout) которого передается по конвейеру на ввод (stdin) процесса для» grep key «, а также для процесса для» less «. Каждый процесс принимает входные данные от предыдущего процесса и производит выходные данные для следующего процесса через стандартные потоки. Каждый « | » сообщает оболочке, что нужно подключить стандартный выход команда слева к стандартному вводу команды справа с помощью механизма межпроцессного взаимодействия, который называется (анонимный) канал, реализованный в операционной системе. Каналы являются однонаправленными; данные проходят по конвейеру слева направо.

  • 1 Конвейеры в интерфейсах командной строки
    • 1.1 Поток ошибок
    • 1.2 Pipemill
    • 3.1 Сетевые каналы
    • 4.1 Другие операционные системы

    Конвейеры в интерфейсах командной строки

    Все широко используемые оболочки Unix ls имеет специальную синтаксическую конструкцию для создания конвейеров. При любом использовании команды записываются последовательно, разделенные символом ASCII вертикальной черты « | » (который по этой причине часто называют «трубкой персонаж»). Оболочка запускает процессы и устанавливает необходимые соединения между их стандартными потоками (включая некоторый объем памяти buffer ).

    Поток ошибок

    По умолчанию стандартные потоки ошибок («stderr ») процессов в конвейере не передаются через труба; вместо этого они объединяются и направляются в консоль. Однако многие оболочки имеют дополнительный синтаксис для изменения этого поведения. Например, в оболочке csh использование « | » вместо « | » означает, что стандартный поток ошибок также должен быть объединен со стандартным выводом и переданы в следующий процесс. Bourne Shell также может объединять стандартную ошибку с | начиная с bash 4.0 или с помощью 2>1 , а также перенаправлять ее в другой файл.

    Pipemill

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

    Однако оболочка может выполнять обработку напрямую, используя так называемые mill или pipemill (поскольку команда при используется для «измельчения» результатов начальной команды). Эта конструкция обычно выглядит примерно так:

    команда | пока читаем -r var1 var2. ; do # обрабатывать каждую строку, используя переменные, проанализированные в var1, var2 и т. д. # (обратите внимание, что это может быть подоболочка: var1, var2 и т. д. будут недоступны # после завершения цикла while; некоторые оболочки, такие как zsh и более новые # версии оболочки Korn обрабатывают команды слева от оператора pipe # в подоболочке) done

    Такая конвейерная обработка может не работать должным образом, если тело цикла включает команды, такие как cat и ssh , который читает из stdin : на первой итерации цикла такая программа (назовем ее стоком) будет читать оставшийся вывод из команда , и цикл завершится (с результатами в зависимости от специфики слива). Есть несколько способов избежать такого поведения. Во-первых, некоторые стоки поддерживают возможность отключения чтения из stdin (например, ssh -n ). В качестве альтернативы, если стоку не нужно читать какой-либо ввод из stdin , чтобы сделать что-то полезное, ему можно передать в качестве ввода.

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

    Создание конвейеров программным способом

    Конвейеры могут быть созданы под управлением программы. Системный вызов Unix pipe() просит операционную систему создать новый объект анонимный канал. Это приводит к появлению двух новых открытых файловых дескрипторов в процессе: конец канала только для чтения и конец только для записи. Концы каналов выглядят как обычные анонимные файловые дескрипторы, за исключением того, что у них нет возможности поиска.

    Чтобы избежать взаимоблокировки и использовать параллелизм, процесс Unix с одним или несколькими новыми каналами обычно вызывает fork() для создавать новые процессы. Затем каждый процесс закроет конец (и) канала, который он не будет использовать, прежде чем производить или потреблять какие-либо данные. В качестве альтернативы процесс может создавать новые потоки и использовать канал для связи между ними.

    Именованные каналы также могут быть созданы с помощью mkfifo () или mknod () , а затем представлены как входной или выходной файл для программ как они вызываются. Они позволяют создавать многопутевые каналы и особенно эффективны в сочетании со стандартным перенаправлением ошибок или с tee .

    Реализация

    В большинстве Unix-подобных систем все процессы конвейер запускается одновременно с их потоками, соответствующим образом подключенными, и управляется планировщиком вместе со всеми другими процессами, запущенными на машине. Важным аспектом этого, отличающим каналы Unix от других реализаций каналов, является концепция буферизации : например, программа-отправитель может производить 5000 байтов за секунду, и принимающая программа может принимать только 100 байтов в секунду, но данные не теряются. Вместо этого вывод программы-отправителя сохраняется в буфере. Когда принимающая программа готова к чтению данных, следующая программа в конвейере читает из буфера. В Linux размер буфера составляет 65 536 байт (64 КБ). Доступен сторонний фильтр с открытым исходным кодом под названием bfr для предоставления при необходимости буферов большего размера.

    Сетевые каналы

    Такие инструменты, как netcat и socat, могут подключать каналы к сокетам TCP / IP .

    История

    Концепция конвейера была изобретена Дугласом Макилроем и впервые описана на страницах руководства в Версия 3 Unix. Макилрой заметил, что большую часть времени командные оболочки передают выходной файл из одной программы в качестве входных данных в другую.

    Его идеи были реализованы в 1973 году, когда («в одну лихорадочную ночь», — писал Макилрой) Кен Томпсон добавил системный вызов pipe () и каналы в оболочку и несколько утилит в версии 3 Unix. «На следующий день, — продолжил Макилрой, — произошла незабываемая оргия шуток, когда все присоединились к азарту сантехники». Макилрой также приписывает Томпсону обозначение | , которое значительно упростило описание синтаксиса конвейера в версии 4.

    Несмотря на то, что конвейеры Unix были разработаны независимо, они связаны с «связью» и предшествовали ей. files ‘, разработанный Кеном Лохнером в 1960-х годах для Дартмутской системы разделения времени.

    . В Тони Хоаре последовательные процессы обмена (CSP) получили дальнейшее развитие трубки Макилроя.

    Робот на значке Apple Automator, который также использует концепцию конвейера для связывания повторяющихся команд вместе, держит конвейер в знак уважения к исходной концепции Unix.

    Другие операционные системы

    Эта функция Unix была заимствована другими операционными системами, такими как Taos и MS-DOS, и в конечном итоге стало шаблоном проектирования конвейеров и фильтров разработки программного обеспечения.

    См. также

    • Все является файлом — описывает одну из определяющих особенностей Unix; конвейеры воздействуют на «файлы» в смысле Unix
    • Анонимный конвейер — структура FIFO, используемая для межпроцессного взаимодействия
    • GStreamer — основанная на конвейере мультимедийная структура
    • Конвейеры CMS
    • Iteratee
    • Именованный канал — постоянные каналы, используемые для межпроцессного взаимодействия
    • Замена процесса — синтаксис оболочки для подключения нескольких каналов к процессу
    • GNU parallel
    • конвейер (вычисления) — другой компьютер связанные конвейеры
    • Перенаправление (вычисление)
    • Тройник (команда) — общая команда для извлечения данных из конвейера
    • конвейер XML — для обработки файлов XML
    • xargs

    Ссылки

    Внешние ссылки

    • История нотации каналов в Unix
      • Первоначальная записка Дуга Макилроя 1964 года, в которой впервые предлагается концепция канала

      Конвейер в Unix или Linux 1 мин для чтения

      Конвейер в Unix или Linux

      Unix и Linux, две наиболее широко используемые операционные системы в мире, во многом обязаны своей мощи и универсальности элегантной концепции конвейеризации. Конвейеризация — это фундаментальная функция, которая позволяет пользователям подключать команды, создавая бесперебойный поток данных от одного процесса к другому. Это краеугольный камень интерфейса командной строки, позволяющий пользователям использовать весь потенциал своих систем. В этой статье мы рассмотрим концепцию конвейера, его синтаксис и множество практических приложений, которые делают его незаменимым инструментом для любого пользователя Unix или Linux. Являетесь ли вы опытным системным администратором или любознательным новичком, понимание piping является ключом к раскрытию истинного потенциала этих операционных систем.

      Что такое конвейер в Unix или Linux?

      Конвейеризация основана на простой, но изобретательной предпосылке. Она позволяет вам принимать выходные данные одной команды и передавать их непосредственно в качестве входных данных в другую, создавая непрерывный поток данных между процессами. Это достигается с помощью символа трубопровода, представленного вертикальной чертой «|». Когда вы используете символ «|» в командной строке, вы, по сути, приказываете системе отправить вывод предыдущей команды в следующую. Вот простой пример:

      ls | grep .txt

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

      Примеры конвейера в Unix или Linux

      Конечно! Вот несколько практических примеров конвейера в Unix или Linux:

      1. Подсчет строк в текстовом файле:

      Чтобы подсчитать количество строк в текстовом файле, вы можете использовать команду wc (количество слов) в сочетании с конвейером:

      cat file.txt | wc -l

      Эта команда сначала использует команду cat для отображения содержимого «file.txt», а затем передает его в wc -l для подсчета количества строк в файле.

      2. Выполните поиск определенной строки в файлах:

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

      grep "search_term" *.txt

      Эта команда выполняет поиск «search_term» во всех текстовых файлах в текущем каталоге.

      Читать Оповещения в Snort в Linux

      3. Сортировка и удаление дубликатов:

      Чтобы отсортировать список элементов по алфавиту и удалить дубликаты, вы можете использовать команды sort и uniq вместе:

      cat unsorted_list.txt | sort | uniq

      Эта команда берет содержимое «unsorted_list.txt», сортирует их по алфавиту, а затем удаляет повторяющиеся строки.

      4. Вычислите общий размер файлов в каталоге:

      Чтобы вычислить общий размер файлов в каталоге, вы можете использовать du (использование диска) и awk:

      du -sh /path/to/directory | awk '’

      Эта команда использует du для получения информации об использовании каталога на диске, передает ее в awk для извлечения и печати размера в удобочитаемом формате.

      5. Найдите наиболее ресурсоемкие процессы:

      Для определения наиболее ресурсоемких процессов вы можете использовать ps, sort и head вместе:

      ps aux --sort=-%cpu | head

      Эта команда использует ps для составления списка всех процессов, сортирует их по загрузке процессора в порядке убывания, а затем показывает верхние процессы с помощью head.

      6. Извлечение данных из CSV-файла:

      Для извлечения определенных столбцов из CSV-файла вы можете использовать cut и grep вместе:

      cat data.csv | grep "search_criteria" | cut -d, -f2,4

      Эта команда сначала выполняет поиск строк, содержащих «search_criteria», затем использует cut для извлечения 2-го и 4-го столбцов (при условии, что поля разделены запятыми).

      7. Создайте резервную копию каталога:

      Для создания сжатой резервной копии каталога вы можете использовать tar и gzip с конвейером:

      tar -czvf backup.tar.gz /path/to/directory

      Эта команда использует tar для создания архива каталога и передает его в gzip для сжатия в один файл.

      Практическое применение конвейера

      Piping предлагает множество практических приложений в Unix и Linux, что делает его фундаментальным навыком для любого пользователя. Вот несколько примеров:

      1. Фильтрация и обработка данных: Конвейеризация обычно используется для фильтрации и обработки данных. Например, вы можете использовать команду «сортировать» для сортировки списка имен по алфавиту, а затем передать отсортированный список в «uniq», чтобы удалить дублирующиеся записи.
      2. Манипулирование текстом: Вы можете использовать конвейеризацию для манипулирования текстовыми данными и их преобразования. «sed» и «awk» — это два мощных инструмента для обработки текста в сочетании с конвейером.
      3. Автоматизация и написание сценариев: Конвейеризация является ключевым компонентом написания сценариев оболочки. Вы можете создавать сложные сценарии, которые автоматизируют ряд задач, соединяя команды по конвейерам.
      4. Мониторинг системы: Конвейеризация необходима для мониторинга системных ресурсов. Такие команды, как «ps», «top» и «grep», могут передаваться по конвейеру вместе для фильтрации и анализа системных процессов.

      Читать Временные метки файлов в Linux: atime, mtime, ctime. Разъяснения

      Передовые методы конвейеризации

      Помимо основ, продвинутые методы конвейеризации позволяют создавать сложные конвейеры для достижения конкретных целей. Некоторые советы по совершенствованию ваших навыков конвейеризации включают:

      1. Несколько конвейеров: Вы можете объединить несколько команд вместе для создания более сложных конвейеров. Например: command1 | command2 | command3.
      2. Подоболочки: Вы можете использовать подоболочки, созданные с помощью круглых скобок, для изоляции данных внутри конвейера и манипулирования ими. Это позволяет применять различные операции к подмножествам данных.
      3. Именованные каналы: Именованные каналы, также известные как FIFOs (First In, First Out), представляют собой тип канала, который обеспечивает межпроцессное взаимодействие между командами и даже разными пользователями.
      Заключение:

      В мире Unix и Linux конвейер является свидетельством элегантности простоты. Это концепция, которая десятилетиями вдохновляла пользователей, позволяя им эффективно выполнять широкий спектр задач. Благодаря бесшовному соединению команд и потоков данных конвейеризация преобразует сложные операции в серию управляемых шагов. Завершая наше исследование конвейера в Unix и Linux, мы надеемся, что вы получили более глубокое представление о его значимости и универсальности.

      Piping (конвейер) — это не просто техническая функция; это мост, соединяющий пользователей с огромной мощью этих операционных систем. От обработки текста и манипулирования данными до автоматизации и написания сценариев, piping открывает двери для бесконечных возможностей. Овладев искусством конвейеризации, вы будете лучше подготовлены к оптимизации рабочего процесса, сокращению избыточности и достижению большего с меньшими усилиями.

      Теперь, когда вы освоили основы, мы рекомендуем вам глубже погрузиться в мир Unix и Linux. Изучите различные команды и их приложения, поэкспериментируйте с более сложными конвейерами и раскройте потенциал вашей системы. Конвейеризация — это всего лишь одна часть головоломки, и при дальнейшем изучении вы откроете для себя еще больше сокровищ, которые могут предложить эти операционные системы.

      Читать Как найти IP-адрес в командной строке Linux

      FAQ (часто задаваемые вопросы) Связанные с конвейером в unix или linux:

      Вот несколько часто задаваемых вопросов, связанных с конвейеризацией в UNIX или LINUX.

      1. Что такое конвейеризация в Unix и Linux?

      Конвейеризация — это функция, которая позволяет вам подключать выходные данные одной команды к входным данным другой, создавая непрерывный поток данных. Она обозначается символом «|» в командной строке.

      2. Как мне использовать конвейер в Unix и Linux?

      Чтобы использовать конвейер, просто введите команду, за которой следует символ «|», а затем другую команду. Например, чтобы составить список файлов в каталоге и выполнить поиск определенного файла в этом списке, вы можете использовать: ls | grep filename.

      3. Каковы некоторые практические применения конвейера?

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

      4. Могу ли я объединить несколько команд вместе с помощью конвейера?

      Да, вы можете объединить несколько команд в конвейер. Например, вы можете использовать серию команд, соединенных конвейером, для выполнения сложных операций с данными.

      5. Являются ли конвейеризация и перенаправление одним и тем же?

      Нет, конвейеризация и перенаправление — это разные понятия. Конвейеризация — это соединение выходных данных одной команды со входными данными другой, в то время как перенаправление включает в себя изменение того, откуда поступает ввод или вывод команды (например, использование «>» для перенаправления выходных данных в файл).

      6. Является ли конвейеризация эксклюзивной для командной строки в Unix и Linux?

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

      7. Существуют ли какие-либо ограничения для конвейера?

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

      Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

      Использование потоков, конвейеров и перенаправлений в Linux

      img

      Операционные системы Unix традиционно используют такие понятия, как стандартный ввод, вывод и вывод ошибки. Чаще всего ввод — это клавиатура, а вывод это на кран. Но конечно же мы можем выводить исполнение какой-то команды в файл и передавать другой команде, потому что работая в Linux, создается такая последовательность из команд, т.е результат предыдущей мы отправляем следующей и т.д

      Целью данной статьи является рассмотреть:

      • Перенаправление стандартных ввода, вывода и ошибок;
      • Передача вывода одной команды в качестве аргументов другой;
      • Получение выходных данных в файл и на стандартный вывод;
      • Stdin (0) – ввод
      • Stdout(1) – вывод
      • Stderr (2) – вывод ошибки
      • > — передать в
      • >> — дописать в
      • — взять из
      • | — отправить следующей команде
      • Tee — отправить в файл и на стандартный вывод
      • Xargs – построчно передать на ввод команде

      Для начала воспользуемся командой wc которая посчитает, количество слов, символов и строк в файле wc test.txt .

      wc test.txt

      Мы можем указать данной команде другой input . Мы можем ей сказать взять информацию из файла, т.е. записать вот таким образом wc т.е. данной команде передать информацию из этого файла. И данная команда отработав посчитает в принципе то же самое. В таком варианте команда не знает с каким файлом она работает ей просто поступает вывод из файла. Файл выводит свой результат этой команде. Такая стрелочка редко используется, чаще используется стрелка в другую сторону. Например, мы можем список файлов вывести командой ls . А можем сказать, чтобы данная команда отправила результат не на наш стандартный вывод т.к. результат всех наших команд по умолчанию выводится в консоль, а например в файл ls > list.txt . По сути означает выполнить команду, а результат передать в файл. Фал можно посмотреть командой cat list.txt .

      cat list.txt

      И мы можем убедится, что в данном файле находится перечень, всего что находилось в данной папке. Если мы выполним еще раз команду ls > list.txt , то данный файл каждый раз будет перезаписываться. Если же мы хотим, чтобы наш файл не перезаписывался, а дописывался, используем другую стрелочку ls >> list.txt .

      cat list.txt

      И теперь вы можете видеть, что файл стал больше. Т.е. у нас записалось, то что было, а затем еще раз добавилось. Если опять выполнить команду со стрелочками >> , то опять допишется информация в файл. Вот таким образом работают “стрелочки”.

      Стандартный вывод ошибок.

      Мы можем, например, сказать машине, выведи нам содержимое папки bob , которая не существует ls bob > result.txt , естественно мы получим ошибку которую система вывела на экран. Экран является стандартным выводом ошибок. В нашем случае нет папки bob и нет файла resut.txt . Если мы хотим отправить ошибку в файл, так же как результат выполнения команды, то ls bob 2> result.txt , вспоминаем основные понятия, в которых было указанно, что 2 – это стандартный вывод ошибки.

      cat result.txt

      Следовательно, на экране мы уже не видим ошибки, потому что она отправилась в указанный файл.

      Кстати мы можем объединить стандартный вывод команды и стандартный вывод ошибки. Например: ls bob > result.txt 2> error.txt . Выведи содержимое папки bob в файл result.txt , а если возникнет ошибка внеси в файл error.txt .

      В таком случае и команда выполнится и что-то будет в файле и если ошибка возникнет, то она будет записана в файл error.txt . Это можно применять на практике, когда мы что-то делаем и предполагаем, что в процессе выполнения возникнут ошибки, то используя данную конструкцию данные ошибки мы все можем отправить в отдельный файл.

      Конвейер

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

      Например, выполнить команду ls и далее мы могли стрелочкой отправлять результаты выполнения команды в файл, т.е. мы меняли только стандартный вывод, а не передавали другой программе. А можем выполнить ls | grep r , т.е. получить содержимое и передать по конвейеру команде сортировки и сказать отсортировать по наличию буквы r , а если перенаправить еще вывод в файл, то cat имя файла , мы сможем увидеть информацию в файле.

      cat имя файла

      Но есть другая команда tee которая позволяет работать немного удобнее. Например: ls | tee output.txt . Те данная команда выводит информацию сразу на экран и в указанный файл. Что достаточно удобно с точки зрения работы с выводами.

      И еще одна команда xargs – она построчно работает с выводами. Если у нас есть какая-то команда, которая выдает нам вывод в виде нескольких строк ответа, то мы можем эти строки построчно передавать этой команде, т.е. не одной кашей, а построчно. Например find . –name “*.txt” найти все файлы в текущем каталоге с расширением txt . И если бы мы захотели удалить все эти файлы нам бы пришлось построчно их удалять, но мы можем сказать, чтобы выходные данные были переданы по конвейеру xargs и удалить.

      find . –name “*.txt” | xargs rm -f

      ind . –name “*.txt” | xargs rm -f

      Как видите после данной конструкции команд файлов не осталось. Т.е. данные построчно передались на команду удаления, которая построчно каждый файл с ключом –f (принудительно) их и удалила.

      Ленточный конвейер для линейных данных

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

      У меня нет реализации этих алгоритмов, достойной публикации, поэтому я ограничусь общей идеей и схемой реализации. Примерные схемы даны на C++, в них используется виртуальное и множественное наследование, а также шаблоны.

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

      Давайте рассмотрим возможную реализацию программного интерфейса преобразователя. В самом простом варианте возможны три типа преобразователей:

      class Source < … public: T getData(); … >; class Sink < … public: void putData(T); … >; class Universal < … public: void process(); >; 

      Рис 1. Схема простой реализации конвейера.

      Вот тривиальный пример такого конвейера, «сумматор» (он читает числа из std::cin, складывает их по парам, печатает в std::cout, это рабочая программа на C++):

      #include //считывает число из std::cin class IntSource < public: int getData() < int next; std::cin>>next; return next; > bool good() const < return std::cin.good(); >>; //печатает число в std::cout class IntSink < public: void putData(int data) < std::cout>; //читает пару чисел из IntSource и выдаёт их сумму в IntSink class IntUniversal < IntSource source; IntSink sink; public: void process() < int i1 = source.getData(); int i2 = source.getData(); if( good() ) sink.putData(i1+i2); >bool good() const < return source.good(); >>; int main()
      • первым элементом в конвейере является Source;
      • последним элементом служит Sink;
      • преобразователь Sink может непосредственно предшествовать только другому преобразователю Sink, преобразователь Source может непосредственно следовать только за другим преобразователем Source;
      • преобразователь Universal должен следовать за Source и предшествовать Sink.
      • Sourcek::getData
      • .
      • Sourcen::getData
      • Universal::process,
      • Sinkl::putData
      • .
      • Sink1::putData
      • Universal::process.
      Улучшаем конвейер

      Использование Source и Sink накладывает ограничения на реализацию процедур, а также на их эффективность. Программисту предоставлялось бы больше удобства, если бы конвейер выглядел так: Universal, …, Universal. Воплотить это желание в жизнь можно добавив то, чего действительно не хватает конвейеру: “ленту”. Лентой у нас будет служить область памяти, хранящая данные, находящиеся “между” преобразователями. Как только преобразователь производит данные, они помещаются на ленту, откуда их может считать следующий преобразователь. Конвейер теперь усложнился и не может управляться сам, как было раньше, поэтому требуется специальный “менеджер конвейера”, следящий за лентой и за преобразователями. Преобразователи теперь будем строить в виде наследников от базовых классов с интерфейсом. Вот их упрощённый вид:

       class UniversalBase < public: virtual void process()=0; >; template class UniversalSource; template class UniversalSink; //универсальный преобразователь для помещения в начало конвейера template class UniversalSource : public virtual UniversalBase < UniversalSink* next; protected: void putOnBelt(const S&); >; //универсальный преобразователь для помещения в конец конвейера template class UniversalSink : public virtual UniversalBase < UniversalSource* prev; protected: T getFromBelt(); >; //универсальный преобразователь, считывающий данные типа T и выдающий данные типа S template class Universal : public UniversalSink, public UniversalSource < >; 

      Рис 2. Схема улучшенной реализации конвейера.

      Функция process реализуется в каждом конкретном преобразователе по-своему, и выполняет суть его назначения. Её задача — произвести некоторое количество данных и передать их менеджеру конвейера для помещения на ленту. Для этого process вызывает функцию putOnBelt, определённую в базовом классе. Важно понимать, что process каждого преобразователя может вызываться несколько раз, она должна произвести, некоторое разумное количество данных (например, одну единицу) и завершиться. Как только реализации process требуются входные данные, она обращается к менеджеру, вызывая getFromBelt.

      Вот пример того же сумматора, но реализованного с использованием новой концепции конвейера. Теперь это уже не полноценная программа, ей не хватает реализации менеджера конвейера.

      #include #inlcude //считывает число из std::cin class IntSource : public Belt::UniversalSource < public: void process() < int data; if( std::cin>>data ) putOnBelt(data); > >; //печатает число в std::cout class IntSink : public Belt::UniversalSink  < public: void process() < if(!hasData() ) return; std::cout>; //читает пару чисел с ленты и кладёт их сумму на ленту class IntUniversal : public Belt::Universal  < public: void process() < int i1 = getFromBelt(); int i2 = getFromBelt(); if(!good() ) return; putOnBelt(i1+i2); >>; int main() < IntSource source; IntUniversal universal; IntSink sink; (source >> universal >> sink).process(); > 

      Здесь использованы функции и классы, о которых ранее не говорилось:

      bool UniversalSink::hasData(void); bool UniversalSink::good(void); template class UnboundedBelt : public Universal ; template class RightBoundedBelt : public UniversalSink ; template LeftBoundedBelt : public UniversalSource ; class Belt : public UniversalBase ; template UnboundedBelt operator >> (Universal&,Universal&); template LeftBoundedBelt operator >> (UniversalSource&,Universal&); template RightBoundedBelt operator >> (Universal&,UniversalSink&); template Belt operator >> (UniversalSource&,UniversalSink&); 

      Проблема определения конца данных может быть решена следующим образом: определяется виртуальная функция bool UniversalSource::hasData(), реализация которой по-умолчанию основывается на правиле — считаем, что данные закончились, если process ничего не выдал за итерацию.

      Подходы к реализации менеджера конвейера

      Функция process вызывается менеджером конвейера. Отличия разных реализаций менеджера конвейера могут заключаться в том, в каком порядке вызывать функции process различных преобразователей. Желательно, чтобы на ленте не накапливался избыток данных, так как это увеличит расход памяти, а также, желательно избегать слишком глубоких стеков вызовов.

      Функция getFromBelt считывает данные с ленты, если они имеются на ней, а в противном случае, она запускает process у предыдущего преобразователя до тех пор, пока он не выдаст какую-нибудь порцию данных на ленту. putOnBelt просто-напросто помещает данные на ленту. Она может вызывать process следующего преобразователя, чтобы он сразу же обработал их, но это не обязательно, и создаёт трудности, о которых — чуть позже.

      Таким образом, стек вызовов в простом случае принимает вид:

      • .
      • менеджер конвейера
      • UniversalSink::getFromBelt()
      • Преобразовательn2::process()
      • менеджер конвейера
      • UniversalSink::getFromBelt()
      • Преобразовательn1::process()
      • менеджер конвейера
      • менеджер конвейера не имеет права вызывать функцию process преобразователя, если функция process этого же преобразователя уже находится в стеке вызовов. (A)
      • менеджер конвейера не имеет права вызывать функцию process преобразователя, если функция process более левого преобразователя находится в стеке вызовов.
      • Рекурсивный. Вызывает process непосредственно только у последнего преобразователя, остальные запускаются рекурсивно при необходимости.
      • Последовательный. Вызывает process по очереди (слева направо), “нарабатывая” данные на ленту. Как только решит, что данных достаточно, переходит на один преобразователь правее. Для этого варианта желательно наличие оценок о том, сколько данных «потребляет» тот или иной преобразователь за одну итерацию process.
      Возможные плюшки
      • операцию “заглядывания вперёд”: преобразователь считывает данные, но фактически они не удаляются с ленты, и возвращаются по его требованию;
      • “коробки конфет”: выдача данных порциями, что позволяет повысить эффективность при работе с большим количеством мелких данных, например, символами. По конвейеру передаётся сразу “коробка”, вместо передачи “по одной конфете”, что позволяет избежать вызовов функций на каждую единицу данных;
      • “умный” распределитель памяти (аллокатор), который позволит избегать постоянных выделений/удалений динамической памяти, при работе конвейера, и даже может избавить от лишних операций копирования;
      • выполнение в нескольких потоках. В случае конвейера это всегда возможно, и эффективно, если каждый из преобразователей однопоточен.

      Также, по сравнению с “реализаций в лоб” большим преимуществом является: независимость отдельных частей программы друг от друга (каждый преобразователь знает только о типах его входных и выходных данных, может не знать ничего о преобразователях, к которым он подключен входом и выходом), возможность безболезненно включать новые преобразователи и менять их порядок.

      Заключение
      • изучавших тему, читавших литературу по этой теме, ужасающихся терминологией или концепцией;
      • знающих английский язык, ужасающихся прецедентами использования английских слов в идентификаторах;
      • многочисленных приверженцев отличных от моего (и без сомнения, более правильных) стилей кодинга;
      • ненавистников C++ или множественного наследования;
      • программистов, не знакомых с использованными конструкциями C++.
      • программирование
      • конвейеры
      • трансляторы

Добавить комментарий

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