Channels in Clojure

In Clojure, we use channels from the core.async library to achieve similar functionality to Go’s channels. Channels are the pipes that connect concurrent processes. You can put values into channels from one process and take those values into another process.

(ns channels-example
  (:require [clojure.core.async :as async :refer [>! <! go chan]]))

(defn main []
  ; Create a new channel with `chan`
  ; Channels are typed by the values they convey
  (let [messages (chan)]
    
    ; Put a value into a channel using the `>!` function
    ; Here we put "ping" to the `messages` channel we made above,
    ; from a new go block (similar to a goroutine)
    (go (>! messages "ping"))
    
    ; The `<!` function takes a value from the channel
    ; Here we'll take the "ping" message we sent above and print it out
    (go (println (<! messages))))
    
    ; Sleep to allow async operations to complete
    (Thread/sleep 100))

(main)

When we run the program, the “ping” message is successfully passed from one process to another via our channel.

$ clj -M channels_example.clj
ping

By default, puts and takes block until both the sender and receiver are ready. This property allowed us to wait at the end of our program for the “ping” message without having to use any other synchronization.

In Clojure’s core.async, we use go blocks to create lightweight processes similar to goroutines. The >! and <! operations are used within go blocks for putting to and taking from channels, respectively.

Note that in this Clojure example, we’ve added a small delay at the end of the main function to allow the asynchronous operations to complete before the program exits. This is because Clojure’s REPL or script execution might terminate before the asynchronous operations have a chance to run, unlike Go’s runtime which waits for all goroutines to complete.