2015-06-17 3 views
1

У меня есть gzipped-файл, который я разделил на 3 отдельных файла: xaa, xab, xac. Я делаю ФИФОАномалия подачи труб

mkfifo p1 

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

cat p1 p1 p1 | tee >(sha1sum > sha1sum_new.txt) | gunzip > output_file.txt 

Это работает просто отлично, если я кормлю трубу от другого терминал с

cat xaa > p1 
cat xab > p1 
cat xac > p1 

но если я кормить трубу с одной линией,

cat xaa > p1; cat xab > p1; cat xac > p1 

принимающий конвейер зависает, контрольная сумма не создается, и хотя выходной файл создается, он усекается - но на сумму, меньшую, чем конечный размер файла.

Почему поведение во втором случае отличается от первого?

ответ

1

Интересующий вопрос. Как упоминается в другом ответе, у вас есть состояние гонки, и я в этом уверен. Фактически, у вас есть условие гонки в обоих случаях, но в первом вам просто повезло, этого не происходит, потому что, возможно, ваши файлы маленькие и могут быть прочитаны перед входом в следующую командную строку. Позвольте мне объяснить.

Итак, немного фона первый:

  1. cat открывает каждый файл, вы кормите его в качестве аргумента последовательно, выводит его на выход, а затем закрывает файл и переходит к следующему файлу. Точные данные о том, будет ли cat каждый раз открывать каждый файл или сначала открывать их все, а затем записывать каждый файл, могут быть разными, но это не относится к обсуждению. В обоих случаях у вас будет состояние гонки
  2. Сценарий open(2) будет блокироваться на FIFO/pipe, пока не откроется другой конец. Например, если процесс pid1 открывает FIFO для чтения, open(2) будет блокироваться, пока, скажем, pid2 не откроет FIFO для записи. Другими словами, открытие FIFO, у которого нет активных читателей или писателей, неявно синхронизирует оба процесса и гарантирует, что процесс не будет читаться из канала, у которого пока нет писателя, или что писатель не будет писать в канал, у которого пока нет читателя , Но, как мы увидим, это будет проблематично.

Что на самом деле происходит

Когда вы сделаете это:

cat xaa > p1 
cat xab > p1 
cat xac > p1 

Вещи очень медленно, потому что люди медленно. После ввода первой строки cat открывает p1 для записи. Другой cat заблокирован при открытии его для чтения (или, может быть, еще нет, но предположим, что это так). Как только оба процесса cat открывают p1 - один для записи, другой для чтения - данные начинают течь.

И тогда, прежде чем вы даже есть шанс войти в следующую командную строку (cat xab >p1), весь файл проходит через трубу и все довольны - процесс cat читатель видит конец файла на трубе, вызывает close(2) , писатель cat заканчивает запись файла и закрывает p1. Читатель cat переходит к следующему файлу (это p1), открывает его и блокирует, потому что ни один активный писатель еще не открыл фиолетовый.

Затем, вы, замедленный человек, введите следующую командную строку, которая заставляет другой процесс записи cat открыть FIFO, который разблокирует другой cat, который ждет открытия для чтения, и все повторится. И снова для третьей командной строки.

Когда вы помещаете все в одну строку в оболочку, все происходит слишком быстро.

Проповедуем три вызова 3 cat. Назовите это cat1, cat2 и cat3:

cat1 xaa > p1; cat2 xab > p1; cat3 xac > p1 

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

Однако, это может быть просто так, что cat1 закончил писать все для p1 и выходов, оболочка переходит к cat2, который открывает FIFO и начинает писать снова содержимое p1, и читатель cat не было шанс закончить чтение того, что написал cat1, и теперь внезапно читатель cat «думает», что он все еще читает из первого файла (первый p1), но в какой-то момент он начинает считывать данные, которые cat2 начал толкать в трубы (как будто это было в первых p1). Он не знает, что первая «копия» данных завершена, если cat2 быстрее и открывает FIFO, прежде чем читатель cat закончит читать то, что написал cat1.

Да, тонкий, но это именно то, что происходит.

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

Как исправить это

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

Поэтому я предлагаю вместо этого:

  1. Создать постоянный процесс писателя, который только поддерживает буфер FIFO открыт для записи, даже если он никогда не будет на самом деле писать. Это просто, чтобы убедиться, что нет окна времени, когда никакие авторы не активны, и читатель пытается прочитать. Для этого просто cat стандартный ввод для p1 в фоновом режиме: cat >p1 &. Когда вы закончите, убейте фоновое задание.
  2. Откройте трубу только один раз в процессе считывания. Это можно сделать либо с помощью cat p1 | tee >(sha1sum ...), либо с использованием метода, предложенного в другом ответе (tee >(...) <p1). В конце концов, открытие FIFO однажды должно быть достаточно независимо от того, насколько сложна ваша система; FIFO по своей природе всегда дают вам данные в первом порядке.

Сохраните фоновый рисунок cat писателя, пока вы знаете, что есть вероятность появления новых файлов/новых авторов, открывающих FIFO и использующих его. Не забудьте завершить фоновое задание, когда вы знаете, что вход окончен.

1

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

tee >(sha1sum > sha1sum_new.txt) < p1 | gunzip > output_file.txt 

и кормить p1 с помощью одной команды

cat xaa xab xac > p1 

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

+0

Хорошая мысль. Благодарю. Но пример, который я дал, является лишь примером, и я, вероятно, не буду иметь контроль над концом, который передает канал; Я мог бы застрять в файлах, проходящих через один за раз. Все еще озадачен, почему он работает, когда труба подается по одной линии за раз, но не удается, когда каждая кошка встречается на одной линии. Кажется, что два должны быть эквивалентными. – SixDegrees

+0

@SixDegrees Не полагайтесь на это. За исключением очень простых случаев, работать с трубами довольно сложно, и вам действительно нужно понять, что происходит под капотом, чтобы иметь шанс на это. Рассмотрим трубы - отличный источник тупиков. :) – lcd047

+0

@SixDegrees Действительно, это состояние гонки - оно тонкое, но оно есть. Этот ответ описывает, как исправить это довольно хорошо; мои попытки объяснить, почему это происходит. Если вы не можете контролировать, как данные поступают в FIFO, см. Мое предложение в конце моего ответа - возможно, это сработает для вас. –