Sign In

  • Username:
  • Password:

Upload File

  •  

Using Channel to Synchronize Goroutines

Channels are the pipes that connect concurrent goroutines. We can send and receive values from one goroutines to another with channel.
And channels can also used to synchronize execution across goroutines.
I also wrote a Go library Barbarian which provides a convenient interface to run your program concurrently.

0x01 go foo()

A goroutine is a lightweight thread of execution, whose stack size at start is only about 4k.
Hence its common for Go applications to have thousands of Goroutines running concurrently.

package main

import (
    "fmt"
    "time"
)

func hello() {
    fmt.Println("Hello goroutine")
}
func main() {
    go hello()

    time.Sleep(1 * time.Second)
    fmt.Println("main function")
}

Notice that goroutines will call returns immediately when start. So if we comment out sleep(), the program will print main function and exit.

But how should we block the main thread until all goroutines are completed gracefully?

Please wait a minute.

0x02 make(chan interface{})

Let’s see how to use channel first.

package main

import (
    "fmt"
    "time"
)

func main() {
    // unbuffered channel.
    ch := make(chan string)

    go func() {
        time.Sleep(1 * time.Second)
        ch <- "Var received!"
    }()

    fmt.Println("Waiting...")
    fmt.Println(<-ch)
}

After 1s, we will see the output.

# amyang @ archlinux in ~/Projects/Sniper on git:master x [21:05:12]
$ go run 1.go
Waiting...
Var received!

Because unbufferd channel will block all operations on the channel until both sender and receiver are ready to communicate.
So unbuffered channel are also called synchronous.
We can use this property to synchronize goroutines.

0x03 blocker := make(chan struct{})

Here are 2 examples, hope them will be of some use to you :)

sleep sort

Sleep Sort is a very funny way to sort an array of non-negative numbers. The princilple is, for number x wait for time proportional to x milliseconds and then print it.

package main

import (
    "fmt"
    "time"
)

func main() {
    data := []int{2, 5, 1, 7, 9, 4, 8, 10, 3, 6}
    Sort(data)
}

// Sort .
func Sort(nums []int) []int {
    blockers := make(chan struct{}, len(nums))
    for _, n := range nums {
        blockers <- struct{}{}
        go Print(n, blockers)
    }

    for i := 0; i < cap(blockers); i++ {
        blockers <- struct{}{}
    }
    return []int{}
}

// Print .
func Print(n int, blocker chan struct{}) {
    defer func() { <-blocker }()
    time.Sleep(time.Duration(n) * time.Millisecond)
    fmt.Println(n)
}

web crawler

from AssassinGo

func (c *Crawler) Crawl(url string, depth int, ret chan interface{}) {
    defer close(ret)

    if depth <= 0 {
        return
    }
    // url has been visited
    if _, ok := c.visitedURLs.Load(url); ok {
        return
    }
    c.visitedURLs.Store(url, true)

    body, nextURLs := c.fetch(url)

    ret <- c.filter(body)

    results := make([]chan interface{}, len(nextURLs))
    for i, u := range nextURLs {
        results[i] = make(chan interface{})
        go c.Crawl(u, depth-1, results[i])
    }

    for i := range results {
        for s := range results[i] {
            ret <- s
        }
    }

    return
}

func main() {
    result := make(chan interface{})
    go Crawl("http://target.com", 4, result)

    for fuzzableURLs := range result {
        for _, url := range fuzzableURLs.([]string) {
            check(url)
        }
    }
}