Go 语言之旅_2(通道、并发互斥)

练习:等价二叉查找树

这里要求实现Walk和Same函数,将树的值依次送到通道里,然后在再Same里进行比较。这里使用到了通道。

  • Walk()函数就按照中序遍历进行输出,遍历到中间节点时不用输出,直接送到通道里就可以了,通道是带有缓冲区的,有10个长度。
  • Same()函数里面先定义了两个10长度的通道,调用Walk()函数用的是并发。然后在循环10次,依次从两个通道里取数据进行比较。
  • 这里通道有缓冲的话,不想数组那样,可以有下标,他就是往里存,往外取,没有用下标去取具体的第几个。
package main

import "golang.org/x/tour/tree"
import "fmt"

// Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。
func Walk(t *tree.Tree, ch chan int) {
	if t == nil {
		return
	}
	if t.Left != nil {
		Walk(t.Left, ch)
	}
	ch <- t.Value//节点值送到通道中
	if t.Right != nil {
		Walk(t.Right, ch)
	}
}

// Same 检测树 t1 和 t2 是否含有相同的值。
func Same(t1, t2 *tree.Tree) bool {
	ch1 := make(chan int, 10)
	ch2 := make(chan int, 10)
	go Walk(t1, ch1)
	go Walk(t2, ch2)
	for i := 0; i < 10; i++ {
		c1 := <-ch1//分别从两个通道中依次取出树输出的数据,并进行比较
		c2 := <-ch2
		if c1 != c2 {
			return false
		}
	}
	return true
}

func main() {
	//fmt.Println(tree.New(2))
	fmt.Println(Same(tree.New(1), tree.New(2)))//两棵不同的树输出false
}

2 sync.Mutex 互斥

不同的Go 程在访问同一个通道的时候,要保证一个Go程一次只能访问一个共享的变量,避免冲突。这里需要利用到互斥(mutual exclusion),Go 标准库里提供了互斥锁(MUtex)这一数据结构来提供这种机制。

  • 需要用到sync.Mutex互斥锁类型及两个方法:LockUnlock。在代码执行前调用Lock,结束之后调用Unlock保证代码的互斥执行。
  • 这里的代码是将Map中的一个键值对的值增加到1000,利用的是1000的Go 程来进行实现。因为Map本身并不是并发安全的,所以需要将Map绑定到互斥锁,利用它对Map的键值对数据调整进行保护。
  • 声明一个结构体,将Map和sync.Mutex添加到里面。在实现对Map进行操作的代码前后用LockUnlock进行保护。
package main

import (
	"fmt"
	"sync"
	"time"
)

// SafeCounter 的并发使用是安全的。
type SafeCounter struct {
	v   map[string]int
	mux sync.Mutex
}

// Inc 增加给定 key 的计数器的值。
func (c *SafeCounter) Inc(key string, i int) {
	c.mux.Lock()
	// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
	c.v[key]++
	//fmt.Println(i)
	c.mux.Unlock()
}

// Value 返回给定 key 的计数器的当前值。
func (c *SafeCounter) Value(key string) int {
	c.mux.Lock()
	// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
	defer c.mux.Unlock()
	return c.v[key]
}

func main() {
	c := SafeCounter{v: make(map[string]int)}
	for i := 0; i < 1000; i++ {
		go c.Inc("somekey", i)
	}

	time.Sleep(time.Second)//这里如果不给一点时间让Go程运行一下,Map里的值还没变就直接输出了,就会输出0
	fmt.Println(c.Value("somekey"))
}

3. Web 爬虫

  • 这里使用一个Map来存储已经找到的URL,但是由于map本生不是并发安全的,所以要绑定互斥锁。
package main

import (
	"fmt"
	"time"
	"sync"
)

type Fetcher interface {
	// Fetch 返回 URL 的 body 内容,并且将在这个页面上找到的 URL 放到一个 slice 中。
	Fetch(url string) (body string, urls []string, err error)
}

type urlMap struct {
	urlM map[string]string
	mux sync.Mutex
}

func (um *urlMap) insert (url string, body string){
	um.mux.Lock()
	defer um.mux.Unlock()
	um.urlM[url] = body
}

// Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。
func Crawl(um *urlMap, url string, depth int, fetcher Fetcher) {
	// TODO: 并行的抓取 URL。
	/*if depth <= 0 {
		return 
	}
	body, urls, err := fetcher.Fetch(url)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("found: %s %q\n", url, body)
	for _, u := range urls {
		go Crawl(u, depth-1, fetcher)
	}
	return	*/
	// TODO: 不重复抓取页面。
	if depth <= 0 {
		return 
	}
	body, urls, err := fetcher.Fetch(url)
	if err != nil {
		fmt.Println(err)
		return
	}
	//fmt.Printf("found: %s %q\n", url, body)
	um.insert(url, body)
	for _, u := range urls {
		go Crawl(um, u, depth-1, fetcher)
	}
	return		
        // 下面并没有实现上面两种情况:
	/*if depth <= 0 {
		return
	}
	body, urls, err := fetcher.Fetch(url)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("found: %s %q\n", url, body)
	for _, u := range urls {
		Crawl(u, depth-1, fetcher)
	}
	return
	*/
}

func main() {
	var um = urlMap{urlM : make(map[string]string)}
	go Crawl(&um, "https://golang.org/", 4, fetcher)
	time.Sleep(1 * time.Second)
	for k,v := range um.urlM {
		fmt.Printf("[%s] \"%s\"\n", k, v)
	}
}

// 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{
	"https://golang.org/": &fakeResult{
		"The Go Programming Language",
		[]string{
			"https://golang.org/pkg/",
			"https://golang.org/cmd/",
		},
	},
	"https://golang.org/pkg/": &fakeResult{
		"Packages",
		[]string{
			"https://golang.org/",
			"https://golang.org/cmd/",
			"https://golang.org/pkg/fmt/",
			"https://golang.org/pkg/os/",
		},
	},
	"https://golang.org/pkg/fmt/": &fakeResult{
		"Package fmt",
		[]string{
			"https://golang.org/",
			"https://golang.org/pkg/",
		},
	},
	"https://golang.org/pkg/os/": &fakeResult{
		"Package os",
		[]string{
			"https://golang.org/",
			"https://golang.org/pkg/",
		},
	},
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值