Welcome! Please see the About page for a little more info on how this works.

0 votes
in IO by

In general, we want to avoiding blocking IO and make it parking. Let us say we make saving to DB nonblocking by a go block.

(defn save-user-info [user]
   (go (...)))

Now if I want to use this function, I want to call it in a go block since I do not want blocking.

(defn register-user [user other-info]
  (go (validate-user ...)
      ...
      (<! (save-user-info user))))

The function save-user and register-user can be used in many places and then we need another go block.

(defn send-welcome-email [user params]
    (go ...
        (<! (register-user user))
        ...))

And again send-welcome-email can be used in other functions (say last-fn) and we then need another go block. So we have created a chain of functions using go since the "origin" one does.

Then if we call last-fn, we have to create several go blocks one-by-one.

My question is: is this the acceptable/correct way to use go? Or my question is wrong.

I am worried because it seems in order to execute one function, we need to create several go (and then channels) and GC them. it is not elegant some how. I do not understand core.async quite in deep but believe go is to make IO non-blocking. On the other side, if we think go as a state machine, then it seems we shall avoid IO in go and make things inside side-effect free.

2 Answers

+1 vote
by
selected by
 
Best answer

Don't put async/go or async/thread inside core function definitions. Make "normal", single-responsibility, decoupled, easy-to-test functions and then connect them with channels in a conveyor-belt-like pipeline. Just like you would do with function composition, but instead of functions communicating directly, they communicate via channels.

You can attach a function to a channel directly by transforming it to a transducer, e.g. (async/chan buf (map save-user-info)). You can connect two channels via async/pipeline(-blocking -async) etc. If your functions use blocking I/O operations (working with database, sending mails, reading files) use blocking constructs (!!, async/thread, async/pipeline-blocking etc). If operations are computations or non-blocking I/O, use parking variations (!, async/go, async/pipeline etc).

+1 vote
by

go blocks in general aren't meant for blocking operations.
In fact, it's rather frequently advertised against it because of limited thread pool that go blocks use - if you do blocking IO in your go blocks then the threads can be blocked for a long time and won't be able to execute pure computations.

You should consider using a completely separate thread pool (ExecutorService et al) or at least clojure.core.async/thread.

Some resources
https://martintrojer.github.io/clojure/2013/07/07/coreasync-and-blocking-io
https://eli.thegreenplace.net/2017/clojure-concurrency-and-blocking-with-coreasync/

...