Golang Funny: Play with Channel

Golang have a funny toy: Channel. Here has some words from the specification:

A channel provides a mechanism for two concurrently executing functions to synchronize execution and communicate by passing a value of a specified element type.

Yep! It is boring and dull. You can’t understand it when you read it first time. Actually, you can think of it as a PIPE or a FIFO queue. It’s lightweight and simple. Channel is not of Golang origin. It also appears in some other languages(embedded). And most of time, it’s a function as a part of a huge, complexly and heavy message queue system.

OK! Let’s have some fun.

Common way: Producer / Consumer

This is the most common way to use a channel. A producer makes things and put them into the channel. And a consumer get them from the channel. On by on, in order. If the channel is full, the producer must wait. Otherwise, if the channel is empty, the consumer must wait.

You can get full source from here. Running it at local is better.

Producer:

func producer(c chan int64, max int) {
    defer close(c)
    for i:= 0; i < max; i ++ {
        c <- time.Now().Unix()
    }
}

The producer makes a number of ‘max’ int64, and put them into the channel ‘c’. Notice that, here is a defer for closing the channel.

Consumer:

func consumer(c chan int64) {
    var v int64
    ok := true
    for ok {
        if v, ok = <-c; ok {
            fmt.Println(v)
        }
    }
}

Read int64s from the channel one by one, and print them on to the screen. When the channel closed, the variable ‘ok’ will be set to ‘false’.

Auto-increment ID generator

If we let the producer makes a sequence of integer. It will be a auto-increment ID generator. I wrote a package that encapsulates it. You can found it at here. The usage can be found at here.

type AutoInc struct {
    start, step int
    queue chan int
    running bool
}

func New(start, step int) (ai *AutoInc) {
    ai = &AutoInc{
        start: start,
        step: step,
        running: true,
        queue: make(chan int, 4),
    }
    go ai.process()
    return
}

func (ai *AutoInc) process() {
    defer func() {recover()}()
    for i := ai.start; ai.running ; i=i+ai.step {
        ai.queue <- i
    }
}

func (ai *AutoInc) Id() int {
    return <-ai.queue
}

func (ai *AutoInc) Close() {
    ai.running = false
    close(ai.queue)
}

Semaphore

Semaphore is a funny usage of Channel. Here is a demo from “Effective Go“. You should have read it, haven’t you? If not, read it now…

I have used the channel as a semaphore in the gearman-go, a package for Gearman job server. You can found the codes here. Line 232, the Worker.exec will be blocked by Worker.limit.

var sem = make(chan int, MaxOutstanding)

func handle(r *Request) {
    sem <- 1    // Wait for active queue to drain.
    process(r)  // May take a long time.
    <-sem       // Done; enable next request to run.
}

func Serve(queue chan *Request) {
    for {
        req := <-queue
        go handle(req)  // Don't wait for handle to finish.
    }
}

Randomized sequence

Of course, we could modify the auto-increment ID generator. Let the producer makes randomized number and put into the channel. But, it’s not funny enough. Right?

Here is another randomized sequence. It was inspired by the specification. It produces 0 or 1 randomly:

func producer(c chan int64, max int) {
    defer close(c)
    for i:= 0; i < max; i ++ {
        select { // randomized select
            case c <- 0:
            case c <- 1:
        }
    }
}

The timeout timer

If a channel was blocked at read/write, it will be blocked forever. Until the channel was closed, then a panic raised. The channel has no timeout timer build in. And it looks like there is no plan to add a timer into channel. But most of time, we need a timeout mechanism. Eg. a producer executed, and something wrong with it, then nothing put into the channel. The consumer was blocked now, until the channel was closed. Close the channel for every time? No! That’s not a good idea.

Here is a solution.

    c := make(chan int64, 5)
    defer close(c)
    timeout := make(chan bool)
    defer close(timeout)
    go func() {
        time.Sleep(time.Second) // wait a second
        timeout <- true // put a bool value into the channel
    }()
    select {
        case <-timeout: // timeout
            fmt.Println("timeout...")
        case <-c: // received a data
            fmt.Println("Read a data.")
    }

Have you noticed the select statement? Which channel receive data, which sub-branch execution. So … do we need more explanation?

This also used in the client of gearman-go, line 238.

Further: Thx @mjq. The time.After is really better solution. And I read the source codes at src/pkg/time/sleep.go, line 74. Internal implementation is same as the above.

One more thing

This is not only a introduction about golang channel, but also a English writing exercise. So, if you have some other ideas about golang channel usage, share them with me! If you found some issues in my blog, plz point out them for me. thx!

[2012.06.26] updated!
Using close to broadcast

Join the Conversation

9 Comments

  1. Nice post. There’s an even easier timeout pattern available now, using time.After. Instead of starting a separate goroutine you can do just:

    select {
    case <-time.After(time.Second):
    fmt.Println("timeout...")
    case <-c:
    fmt.Println("Read a date.")
    }

  2. 1. Common way: Producer / Consumer

    func producer(max int) (c <-chan int) {
    c = make( chan int )
    go func() {
    for i:= 0; i < max; i ++ {
    c <- time.Now().Unix()
    }
    close(c)
    }()

    return

    }

    func consumer(c data ?

  3. 我去居然内容被吞了

    func producer(max int) (c <-chan int) {
    c = make( chan int )
    go func() {
    for i:= 0; i < max; i ++ {
    c <- time.Now().Unix()
    }
    close(c)
    }()

    return

    }

    func consumer(c <- chan int64) {
    var v int64
    ok := true
    for v, ok := range c {
    fmt.Println(v)
    }

    }

    func main() {
    i := 10
    consumer(producer(10))
    }

Leave a comment

Your email address will not be published. Required fields are marked *