Go语言之旅备忘录 – 并发

Go语言游览笔记-并发

首先

為了學習Go語言,我正在進行《Go语言之旅》的學習。這次我會將關於並發性的章節學到的知識寫下來。

这是汇总文章

可能有用的代码片段

筆者使用 VSCode

这(volitional)应该很容易.

光标移动到”类型”一栏

chan type

把以下内容用中文进行同义替换,只需要一个选项:

选择

在条件和情况的范围内,游标移动到了cs所添加的vase中。

select {
case condition:

}

把以下内容用中文进行本地翻译,提供一个选项:

type name interface {

}

每一页的附注

缓冲通道

创建频道时,可以在make函数的第二个参数中指定缓冲区大小。
如果尝试存储超出缓冲区大小的值,则会发生以下错误。

ch := make(chan int, 1)
    c <- 1
    c <- 1 // fatal error: all goroutines are asleep - deadlock!
    fmt.Println(<-c)
    fmt.Println(<-c)

范围和近

for i := range c {
  fmt.Println(i)
}
// for文で書くと...
for i, ok := <-c; ok; {
  fmt.Println(i)
  i, ok = <-c
}

选择

关于select,这篇文章详细介绍了。在A Tour of Go中,select会阻塞直到某个case准备就绪,而这里所说的准备指的是发送和接收的不同情况。

    • 送信時: チャネルのキャパシティに空きがある状態

 

    受診時: チャネルに値が入っている状態

如果不满足这些条件,如果存在默认语句,则执行默认语句;如果不存在,默认情况下将会阻塞处理。

默认选择

如前所述,根据默认语句的有无,选择语句的行为会有很大的不同,所以请注意。

发送邮件时的选择行为

在执行时,由于time.Sleep导致接收方尚未准备好,因此会输出默认值。

c := make(chan int)
go func() {
    time.Sleep(1000 * time.Millisecond)
    fmt.Println(<-c)
}()

select {
case c <- 0:
    fmt.Println("send")
default:
    fmt.Println("default")
}

如果没有默认句子存在,1秒后将输出send(此期间将阻塞处理)。

另外,如果将通道定义如下,由于可以存储在缓冲区中,将会输出 send。
然而,由于主线程直接结束,所以 goroutine 内的 fmt.Println(<-c) 这行代码不会被执行。

c := make(chan int, 1)

选择(select)在接收信号时的行为

同样的是,在这边也是这样

    • default 句がある

defaultが出力される

default 句がない

一秒待機後、receiveが出力される

c := make(chan int)

go func() {
    time.Sleep(1000 * time.Millisecond)
    c <- 0
}()

select {
case <-c:
    fmt.Println("receive")
default:
    fmt.Println("default")
}

练习:等价二进制树

排序二分树是指

在Go之旅中有如下所示

関数 tree.New(k) は、値( k, 2k, 3k, ..., 10k )をもつ、
ランダムに構造化 (しかし常にソートされています) した二分木を生成します。

这个被称为二分搜索树的东西。
(因为链接里也写了简单的逻辑,所以讨厌剧透的人最好不要看)

关于测试方法

在练习中也包括了测试的创建。
因此,我试着使用 testing 包来创建测试。
关于测试和断言的介绍,请参考这里;关于在 vscode 上执行测试,请参考这里。
具体而言,

    1. 随意地去获取 github.com/stretchr/testify/assert 包进行安装。

由于默认情况下,Go 的测试包没有提供断言功能,所以可以自行创建。

创建 ${要测试的文件名}_test.go 文件。

习惯规范。

包名与要测试的文件名相同。

这样可以调用私有方法。

导入 testing 包进行测试。

– 编写测试。
在 launch.json 中添加用于测试的配置。
在 Run 侧边栏中选择创建的配置文件并执行。

翻译为中国语言后有如下一个选项:
代码

package main

import (
    "golang.org/x/tour/tree"
)

func walk(t *tree.Tree, c chan int) {
    if t.Left != nil {
        walk(t.Left, c)
    }
    c <- t.Value
    if t.Right != nil {
        walk(t.Right, c)
    }
}

func Walk(t *tree.Tree, c chan int) {
    walk(t, c)
    close(c)
}

func Same(t1, t2 *tree.Tree) bool {
    c1, c2 := make(chan int), make(chan int)

    go Walk(t1, c1)
    go Walk(t2, c2)

    for {
        v1, ok1 := <-c1
        v2, ok2 := <-c2

        if !ok1 || !ok2 {
            break
        }

        if v1 != v2 {
            return false
        }
    }
    return true
}

func main() {
}

考试代码

package main

import (
    "testing"
    "github.com/stretchr/testify/assert"
    "golang.org/x/tour/tree"
)

func createdExpectedSlice(k int) (result []int) {
    expected := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    for _, v := range expected {
        result = append(result, v*k)
    }
    return
}

func TestWalk(t *testing.T) {
    t.Run("Double", func(t *testing.T) {
        k := 2
        expected := createdExpectedSlice(k)
        c := make(chan int)
        go Walk(tree.New(k), c)
        for _, e := range expected {
            v, ok := <-c
            if ok {
                assert.Equal(t, e, v)
            }
        }
    })
    t.Run("Triple", func(t *testing.T) {
        k := 3
        expected := createdExpectedSlice(k)
        c := make(chan int)
        go Walk(tree.New(k), c)
        for _, e := range expected {
            v, ok := <-c
            if ok {
                assert.Equal(t, e, v)
            }
        }
    })
}

func TestSame(t *testing.T) {
    t.Run("Same tree", func(t *testing.T) {
        t1 := tree.New(1)
        t2 := tree.New(1)
        assert.True(t, Same(t1, t2))
    })

    t.Run("Different tree", func(t *testing.T) {
        t1 := tree.New(1)
        t2 := tree.New(2)
        assert.False(t, Same(t1, t2))
    })
}

为了参考,我也将测试结果附上。

=== RUN   TestWalk
=== RUN   TestWalk/Double
=== RUN   TestWalk/Triple
--- PASS: TestWalk (0.00s)
    --- PASS: TestWalk/Double (0.00s)
    --- PASS: TestWalk/Triple (0.00s)
=== RUN   TestSame
=== RUN   TestSame/Same_tree
=== RUN   TestSame/Different_tree
--- PASS: TestSame (0.00s)
    --- PASS: TestSame/Same_tree (0.00s)
    --- PASS: TestSame/Different_tree (0.00s)
PASS

同步互斥锁

从Inc方法中获取Lock时,会出现致命错误:concurrent map writes。
如果操作被锁定,处理将会暂时中断,并在解锁后恢复处理。

练习:网络爬虫

在Go之旅中,为了等待goroutine内的处理完成,使用time.Sleep(time.Second)。
然而,通过使用sync.WaitGroup,可以捕捉goroutine内的处理结束。
关于WaitGroup的用法,这里更详细。

package main

import (
    "fmt"
    "sync"
)

type Fetcher interface {
    // Fetch returns the body of URL and
    // a slice of URLs found on that page.
    Fetch(url string) (body string, urls []string, err error)
}

func Crawl(url string, depth int, fetcher Fetcher) {
    fetched := make(map[string]bool, 0)
    var mu sync.Mutex
    wg := sync.WaitGroup{}
    wg.Add(1)
    go crawl(url, depth, fetcher, fetched, &mu, &wg)
    wg.Wait()
    fmt.Println(fetched)
}

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func crawl(url string, depth int, fetcher Fetcher, fetched map[string]bool, mu *sync.Mutex, wg *sync.WaitGroup) {
    mu.Lock()

    defer mu.Unlock()
    defer wg.Done()

    // This implementation doesn't do either:
    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)

    fetched[url] = true

    for _, u := range urls {
        if _, ok := (fetched)[u]; !ok {
            wg.Add(1)
            go crawl(u, depth-1, fetcher, fetched, mu, wg)
        }
    }
    return
}

func main() {
    Crawl("https://golang.org/", 4, fetcher)
}

// fakeFetcher is Fetcher that returns canned results.
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 is a populated 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/",
        },
    },
}

对此的感觉

因为对算法周围的知识不足,给人以战斗不利的印象,与其说是语言的问题。

广告
将在 10 秒后关闭
bannerAds