Learning Go - 5 Concurrancy

1. channel管道

默认情况下,在另一端准备好之前,发送和接收都会阻塞,使得go并发进行同步

func sum(a []int, c chan int) {
	sum := 0
	for _, v := range a {
		sum += v
	}
	c <- sum // 将和送入channel
}
func main() {
	a := []int{7, 2, 8, -9, 4, 0}
	c := make(chan int, 2)  // c是有类型的管道,使用前必须创建; 数字是缓冲区大小
	go sum(a[:len(a)/2], c)
	go sum(a[len(a)/2:], c)
	x, y := <-c, <-c // 从 c 中获取,箭头代表方向
	fmt.Println(x, y, x+y)  // -5 17 12,channel实现了 同步;输出顺序不重要!!!go线程调度的问题
}

2. channel的range和close

func fibonacci(n int, c chan int) {
	x, y := 0, 1
	for i := 0; i < n; i++ {
		c <- x
		x, y = y, x+y
	}
	close(c)  // 关闭channel,中断main中的range操作
}
func main() {
	c := make(chan int, 10)
	go fibonacci(cap(c), c)
	for i := range c {  // 不断从c拿值,直到被close。v, ok := <-ch,ch关闭后ok为false了
		fmt.Println(i)
	}
}

3. select 语句

select语句使得一个goroutine在多个通讯操作上等待。哪个ready执行哪个

func fibonacci(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c <- x:
			x, y = y, x+y
		case <-quit:  // 强制等待
			fmt.Println("quit")
			return
		}
	}
}
func main() {
	c := make(chan int)
	quit := make(chan int)
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)  // 先打印完了10次c,才有quit
		}
		quit <- 0
	}()
	fibonacci(c, quit)
}  // output: 0 1 1 2 3 5 8 13 21 34 quit

定时器例子:

func main() {
	tick := time.Tick(100 * time.Millisecond)  // 隔100ms执行一次
	boom := time.After(500 * time.Millisecond)
	for {
		select {
		case <-tick:
			fmt.Println("tick.")
		case <-boom:
			fmt.Println("BOOM!")
			return
		default:  // 当select中其他条件分支都没有准备好时,default分支执行
			fmt.Println("    .")
			time.Sleep(50 * time.Millisecond)
		}
	}
}

4. Exercise: Equivalent Binary Trees

type Tree struct {
    Left  *Tree
    Value int
    Right *Tree
}
// Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。
func Walk(t *tree.Tree, ch chan int) {
	if(t == nil) {
		return
	}
	ch <- t.Value
	Walk(t.Left, ch)
	Walk(t.Right, ch)
}
// Same 检测树 t1 和 t2 是否含有相同的值。
func Same(t1, t2 *tree.Tree) bool {
	ch1 := make(chan int)
    ch2 := make(chan int)
    go Walk(t1, ch1)
    go Walk(t2, ch2)
    for i := 0; i < 10; i++ {
          v1 := <-ch1
          v2 := <-ch2
          if v1 != v2 {
               return false;
          }
     }
     return true
}

5. Exercise: Web Crawler

Crawl函数并行抓取URLs,并且保证不重复

type Fetcher interface {
	// Fetch返回URL的body,并将此页面上的URL放到slice中
	Fetch(url string) (body string, urls []string, err error)
}
// fetched tracks URLs that have been fetched.
// The lock must be held while reading from or writing to the map.
var fetched = struct {
        m map[string]bool
        sync.Mutex  // 匿名!相当于继承
}{m: make(map[string]bool)}
// Crawl使用fetcher从某URL递归爬取页面,直到最大深度。
func Crawl(url string, depth int, fetcher Fetcher) {
	if depth <= 0 {
	        fmt.Printf("Done with %v, depth 0.\n", url)
	        return
	}
	fetched.Lock()  //读写fetched,均要同步
	if _, ok := fetched.m[url]; ok {
	        fetched.Unlock()
	        fmt.Printf("Done with %v, already fetched.\n", url)
	        return
	}
	// then load it concurrently.
	body, urls, err := fetcher.Fetch(url)
	// And update the status in a synced zone.
	fetched.m[url] = true
	fetched.Unlock()

	if err != nil {
	        fmt.Printf("<- Error on %v: %v\n", url, err)
	        return
	}
	fmt.Printf("Found: %s %q\n", url, body)
	done := make(chan bool)
	for i, u := range urls {
	        fmt.Printf("-> Crawling child %v/%v of %v : %v.\n", i, len(urls), url, u)
	        go func(url string) {  // 需要套一层,直接调用go Crawl错误:done标记需要在内部!
	                Crawl(url, depth-1, fetcher)
	                done <- true
	        }(u)
	}
	for i, u := range urls {
	        fmt.Printf("<- [%v] %v/%v Waiting for child %v.\n", url, i, len(urls), u)
	        <-done
	}
	fmt.Printf("<- Done with %v\n", url)
}
func main() {
	Crawl("http://golang.org/", 4, fetcher)
}
// fakeFetcher 是返回若干结果的 Fetcher。
type fakeFetcher map[string]*fakeResult
type fakeResult struct {
	body string
	urls []string
}
func (f fakeFetcher) Fetch(url string) (string, []string, error) {
	if res, ok := f[url]; ok {
		return res.body, res.urls, nil
	}
	return "", nil, fmt.Errorf("not found: %s", url)
}
// fetcher是填充后的fakeFetcher。
var fetcher = fakeFetcher{
	"http://golang.org/": &fakeResult{
		"The Go Programming Language",
		[]string{
			"http://golang.org/pkg/",
			"http://golang.org/cmd/",
		},
	},
	"http://golang.org/pkg/": &fakeResult{
		"Packages",
		[]string{
			"http://golang.org/",
			"http://golang.org/cmd/",
			"http://golang.org/pkg/fmt/",
			"http://golang.org/pkg/os/",
		},
	},
	"http://golang.org/pkg/fmt/": &fakeResult{
		"Package fmt",
		[]string{
			"http://golang.org/",
			"http://golang.org/pkg/",
		},
	},
	"http://golang.org/pkg/os/": &fakeResult{
		"Package os",
		[]string{
			"http://golang.org/",
			"http://golang.org/pkg/",
		},
	},
}
Output:
Found: http://golang.org/ "The Go Programming Language"
-> Crawling child 0/2 of http://golang.org/ : http://golang.org/pkg/.
-> Crawling child 1/2 of http://golang.org/ : http://golang.org/cmd/.
<- [http://golang.org/] 0/2 Waiting for child http://golang.org/pkg/.
<- Error on http://golang.org/cmd/: not found: http://golang.org/cmd/
<- [http://golang.org/] 1/2 Waiting for child http://golang.org/cmd/.
Found: http://golang.org/pkg/ "Packages"
-> Crawling child 0/4 of http://golang.org/pkg/ : http://golang.org/.
-> Crawling child 1/4 of http://golang.org/pkg/ : http://golang.org/cmd/.
-> Crawling child 2/4 of http://golang.org/pkg/ : http://golang.org/pkg/fmt/.
-> Crawling child 3/4 of http://golang.org/pkg/ : http://golang.org/pkg/os/.
<- [http://golang.org/pkg/] 0/4 Waiting for child http://golang.org/.
Found: http://golang.org/pkg/os/ "Package os"
-> Crawling child 0/2 of http://golang.org/pkg/os/ : http://golang.org/.
-> Crawling child 1/2 of http://golang.org/pkg/os/ : http://golang.org/pkg/.
<- [http://golang.org/pkg/os/] 0/2 Waiting for child http://golang.org/.
Done with http://golang.org/pkg/, already fetched.
<- [http://golang.org/pkg/os/] 1/2 Waiting for child http://golang.org/pkg/.
Done with http://golang.org/, already fetched.
<- [http://golang.org/pkg/] 1/4 Waiting for child http://golang.org/cmd/.
Done with http://golang.org/cmd/, already fetched.
<- [http://golang.org/pkg/] 2/4 Waiting for child http://golang.org/pkg/fmt/.
Found: http://golang.org/pkg/fmt/ "Package fmt"
-> Crawling child 0/2 of http://golang.org/pkg/fmt/ : http://golang.org/.
-> Crawling child 1/2 of http://golang.org/pkg/fmt/ : http://golang.org/pkg/.
<- [http://golang.org/pkg/fmt/] 0/2 Waiting for child http://golang.org/.
Done with http://golang.org/pkg/, already fetched.
<- [http://golang.org/pkg/fmt/] 1/2 Waiting for child http://golang.org/pkg/.
Done with http://golang.org/, already fetched.
<- Done with http://golang.org/pkg/os/
<- [http://golang.org/pkg/] 3/4 Waiting for child http://golang.org/pkg/os/.
Done with http://golang.org/, already fetched.
<- Done with http://golang.org/pkg/fmt/
<- Done with http://golang.org/pkg/
<- Done with http://golang.org/
Loading Disqus comments...
Table of Contents