2014-01-04 2 views
9

Рассмотрим следующий фрагмент кода:Clojure - Почему выполнение происходит при блокировке вставки в канал? (Core.async)

(let [chs (repeatedly 10 chan)] 
    (doseq [c chs] 
    (>!! c "hello")) 
    (doseq [c chs] 
    (println (<!! c)))) 

Выполнение этого будет висеть вечно. Почему это?

Если я делаю (go (>! c "hello")), он работает нормально.

ответ

15

Чтобы сделать асинхронную пут, используйте clojure.core.async/put!

(let [chs (repeatedly 10 chan)] 
    (doseq [c chs] 
    (put! c "hello")) 
    (doseq [c chs] 
    (println (<!! c)))) 

Это работает в данном примере, как <!! всегда разблокируют из-за все необходимые путы происходит асинхронно. Обратите внимание на следующие вещи:

  • Блокировка служит в качестве синхронизации ограничения между различными процессами
  • >!! и <!! блокировать основной-нить. go выполняются в основном потоке, но их код модифицируется с помощью макрорасширения, поэтому управление исполнением инвертируется, и их можно припарковать/выполнять последовательно упорядоченными законами логики блокировки/буферизации каналов core.async. Этот метод обычно упоминается как конечная машина IOC (инверсия управления).
  • ClojureScript имеет только одну резьбу. Следовательно, его реализация core.async даже не содержит >!!/<!!. Если вы пишете код, предназначенный для совместимости с ClojureScript, используйте только каналы в пределах go -программы или отправляйте значения из них в функции более высокого порядка, переданные в take!, и всегда делаете их в go -протонах или используйте put!.

Является (go (>! ch v)) эквивалентно (put! ch v)?

Да, но это не то же самое. put! - это обертка API вокруг реализации каналов метода core.async.impl.protocols/WritePortput!. Макроэксплуатация (go (>! ch v)) заканчивается тем же вызовом метода, но обертывает его в большом количестве генерируемого кода состояния машины, чтобы, возможно, припарковать операцию ввода и приостанавливать выполнение команды go до тех пор, пока потребитель не будет готов принять от ch (попробуйте (macroexpand `(go (>! ch v))) самостоятельно). Нерестить блок блокировки только для одной асинхронной операции ввода - это отходы и хуже работает, чем звонить put! сразу. go порождает и возвращает дополнительный канал, из которого вы можете извлечь свои тела. Это позволит вам дождаться завершения его выполнения, которое вы не намеревались сделать в своем примере (с целью асинхронной операции).

+3

Is '(put! C)' эквивалентно '(go (>! C))'? –

+1

Я отредактировал свой ответ, надеюсь, предоставит больше информации. –

+0

Отлично, спасибо! –

5

В этом канале нет буфера, а >!! блокируется. Для этого точного случая обратитесь к the examples. По этой причине они порождают вторую нить - чтобы предотвратить блокировку основного потока. Использование goroutine, как и в вашем вопросе, работает по аналогичному принципу.

Вы также можете создать канал с некоторым буферным пространством - (chan 1)

+0

А, я забыл, что 'puts' также блокируют. Что они блокируют? Это более интуитивно понятно для 'gets', поскольку они блокируют значения, присутствующие в канале. Но когда вы «ставите» что-то, что вы блокируете? –

+1

Ваш вопрос заголовок говорит «блокировка вставки», поэтому вы знали это, по крайней мере, подсознательно. :-) В принципе, он будет блокироваться, пока канал не сможет получить значение. В этом случае это означает, что есть потребитель, готовый немедленно принять значение. Я не знаю детали реализации, но концептуально канал без буфера действует как точка синхронизации. С каналом с буфером он будет удерживать элементы 'n' до блокировки. – Shepmaster

+0

@MarkF По 'puts', вы имеете в виду'>! '/'> !! '? Существует функция 'put!', Которая выполняет те же функции, что и эти функции, но которая не блокирует вызывающий поток. – erikprice

Смежные вопросы