But in general the conclusion still stands. Channels brings unnecessarily complexity. In practice message passing with one queue per goroutine and support for priority message delivery (which one cannot implement with channels) gives better designs with less issues.
Full rebuttal by jerf here: https://news.ycombinator.com/item?id=39317648
Rather than fiddle with Close() calls, from memory what I found worked was setting a short (or in the past) deadline on the Conn (SetReadDeadline). This as it is documented as applying even to an existing blocked call, and makes that blocked call eventually return an error.
So when I wanted to tear down the TCP connection and clean up the various goroutines associated with using the connection, I'd unblock any blocking i/o, then eventually one had an easy Close() call on the connection.
UDP was simpler, as each i/o operation had a short timeout. I've not investigated what could be done for pipe and fifo i/o.