Что не так с именованными каналами, используемыми между двумя процессами?

Я сделал 2 параллельных процесса, которые взаимодействуют с именованным каналом. Я заметил странное поведение: за каждой записью должно следовать чтение или наоборот! Если мы нарушаем правило, программа зависает, а если мы завершаем ее с помощью Ctrl+C, дочерний элемент все равно зависает, что означает, что он просто больше не может повторно читать.

Мой пример:

#!/bin/bash
shopt -u failglob
shopt -s extglob nullglob dotglob

function london (){
   local message answer fifo id return exitcode
   fifo=fifo_$RANDOM.$RANDOM.$RANDOM.$$
   mkfifo ${fifo}
   #trap 'rm -rf "$fifo"' EXIT
   ( berlin $fifo ) &
   id=$!
   echo "parent id: $$, child id: $id"
   message='Greetings from London!(1)'
   echo "$message" > $fifo
   echo "1. parent sent it's 1st message"
   #*****write-to-write error!*****#
   message='Greetings from London!(2)'
   #read -r answer < $fifo
   echo "$message" > $fifo
   echo "2. parent sent it's 2nd message"
   wait
   rm -rf $fifo
}

function berlin (){
   local message answer fifo
   fifo=$1
   read -r answer < $fifo
   echo 'Berlin says:> '"$answer"
   #*****read-to-read error!*****#
   #echo 'next' > $fifo
   read -r answer < $fifo
   echo 'Berlin says:> '"$answer"
}

london

Под точками, где я вставил сообщения «запись для записи» или «чтение для чтения», есть 2 строки с комментариями, которые решают проблему, заставляя меня думать, что вышеуказанное правило таинственным образом выполняется !!! Есть идеи, что происходит?

Вот результат:

parent id: 4921, child id: 4923
1. parent sent it's 1st message
2. parent sent it's 2nd message
Berlin says:> Greetings from London!(1)

Спасибо!

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

Предположим теперь, что я хочу добавить второй дескриптор входного файла для «некоторых» моих команд внутри цикла; как я могу это сделать? Вот моя новая берлинская функция:

function berlin (){
   local message answer fifo
   fifo=$1
   while true; do
      read -r answer
      echo 'Berlin says:> '"$answer"
      #this fails!
      read -r -p "reading from fd0: " <&0
      if [[ $answer = 'quit' ]]; then
         break
      fi
   done < "$fifo" 3>"$fifo"
}

Как видите, мы используем файловый дескриптор 3 для канала, но когда мы пытаемся читать из fd 0, мы на самом деле читаем из fd 3! Есть ли способ добиться этого?


person centurian    schedule 21.12.2012    source источник


Ответы (1)


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

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

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

#!/bin/bash
shopt -u failglob
shopt -s extglob nullglob dotglob

function london (){
   local message answer fifo id return exitcode
   fifo=fifo_$RANDOM.$RANDOM.$RANDOM.$$
   mkfifo ${fifo}
   #trap 'rm -rf "$fifo"' EXIT
   ( berlin $fifo ) & 
   id=$!
   echo "parent id: $$, child id: $id"
   message='Greetings from London!(1)'
   echo "$message" > $fifo
   echo "1. parent sent it's 1st message"
   #*****write-to-write error!*****#
   message='Greetings from London!(2)'
   #read -r answer < $fifo
   echo "$message" > $fifo
   echo "2. parent sent it's 2nd message"
   wait
   rm -rf $fifo
}

function berlin (){
   local message answer fifo
   fifo=$1
   while read -r answer
   do  
       echo 'Berlin says:> '"$answer"
   done < $fifo
}

london
person Spencer Rathbun    schedule 21.12.2012
comment
Да, это правильный ответ, совершенно случайно я нашел это (stackoverflow.com/questions/4290684/), что фактически подтверждает ваш ответ! Спасибо! Конечно, сколько данных вмещает труба? 2^20 байт или как? - person centurian; 21.12.2012
comment
... и последний вопрос: допустимо ли вместо цикла использовать пары чтения-записи? что касается эффективности кода (я подозреваю, что мы снова и снова открываем канал, что плохо!) - person centurian; 21.12.2012
comment
@centurian Именованный канал сохраняется до тех пор, пока вы его не удалите, поэтому очень эффективно использовать цикл while. Чтобы проверить размер труб, используйте ulimit -a в bash, чтобы получить текущий размер трубы. Однако, как я уже упоминал, модуль записи блокируется, если канал заполнен, и при наличии дополнительного места продолжит запись без потери данных. - person Spencer Rathbun; 21.12.2012