Thursday, September 1, 2011

Go channels

Between learning Go, learning JavaScript, and learning web development—what with JSON and AJAX and the difference between PUT and POST and so on—I'm most intrigued by learning Go. With everything else, I'm learning a lot of stuff, detailed but straightforward. With Go, I learning a new way of thinking about synchronization.

Go provides two main features for synchronization: goroutines and channels. Goroutines are a lot like threads, only they're scheduled by the Go Runtime on top of one or a few system threads. But you, the programmer, may pretend goroutines are threads. Channels are new.

Superficially, channels are synchronous queues built in to the language.

ch := make(chan int)
 
a := func() {
    ch <- 42
}
 
b := func() {
   fmt.Println(<-ch)
}

go a()
go b()

In the above code, there are two concurrent functions, a and b, and one channel, ch. The function a sends the value 42 on the channel, and b receives the value (42) from the channel and prints it. Either the send or the receive operation on the channel will block until both the send and receive are ready, at which time both the send and receive occur. (Channels can be asynchronous, but that's another topic.)

In the example above, the channel sends and receives values of type int. But channels' power stems from how they can be used with any other Go type, including functions and other channels. This allows programmers to structure their programs wholly differently than in traditional languages. Consider the code below.

type Request struct {
    // other data go here
    respCh chan *Response
}

type Response struct {
    // other data go here
}

func main() {

    reqCh := make(chan *Request)

    go func() {
        for {
            req := <-reqCh
            // handle request here
            resp := new(Response)
            req.respCh <- resp
        }
    }()

    req := NewRequest()
    reqCh <- req
    resp := <-req.respCh
}

This is a thread-safe request handler. A goroutine (running an anonymous function) continually receives requests on a channel. It returns a response to each request on a channel contained within the request. This is a concise server-client model, scalable as is to many concurrent request handlers and many concurrent request senders. Also, consider how the Request type could contain function closures that execute within the context of the request-handling goroutine.

ch := make(chan func())
ch <- func() {
    fmt.Println("Print this in another goroutine.")
}

Closures make it so that the request handler in the above example doesn't even handle the requests directly; a closure in the Request could contain the details. This opens up numerous design possibilities even in simple programs—without using mutexes or other traditional synchronization primitives.

No comments: