Go语言进阶和依赖管理 | 青训营笔记

101 阅读4分钟

这是我参与「第五届青训营 」笔记创作活动的第2天

一、本堂课重点内容:

  • 本堂课的知识要点有哪些?

    go语言进阶

    1、并发和并行

    2、Go的协程

    3、协程通信

    4、协程通道

    5、WaitGroup计数器

    go的依赖管理

    1、Go的依赖演进

    2、Go依赖配置

    3、工具(go.get/go.mod)

二、详细知识点介绍:

1、并发和并行

并发:

image-20230201174841271

并行:

image-20230201174921569

结论:

go可以充分发挥多核并发的优势,高效运行。

可以说go就是为了并发而生的。

2、Go的协程

image-20230201175435983

协程:

用户态,轻量级线程,栈大小:KB级别。

go语言一次可以创建上万协程。

线程:

内核态,线程上可以运行多个协程,比较消耗资源。

栈大小:MB级别。

3、协程通信

方式一:使用通道交换数据

image-20230201185237664

方式二:使用共享内存完成数据交换

image-20230201185352598

提倡使用通道实现协程通信

4、协程通道

创建方式:make(chan,元素类型,[缓冲大小])

分类:

1、无缓冲通道:同步通道,保证两个协程同步。

2、有缓冲通道:典型生产消费模型。

image-20230201185937088

5、WaitGroup计数器

image-20230201200201472

示例代码:

 //go:build ignore
 // +build ignore
 ​
 package main
 ​
 import "sync"
 ​
 func main() {
    var wg sync.WaitGroup
    wg.Add(5)
    for i := 0; i < 5; i++ {
       go func(j int) {
          defer wg.Done()
          println(j)
       }(i)
    }
    wg.Wait()
 }

运行结果:

image-20230201200800023

6、Go的依赖管理演进

image-20230201200938560

GOPATH

image-20230201201242191

1、项目代码直接依赖src下的代码

2、go get下载最新版本的包到src目录下

弊端:

image-20230201202607326

无法实现pkg的多版本控制

Go Vendor

项目目录下增加vendor文件,所有依赖包副本形式放在$ProjectRoot/vendor

依赖寻址方式: vendor => GOPATH

image-20230201203434320

通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题。

弊端:

image-20230201203626045

问题

无法控制依赖的版本。 更新项目又可能出现依赖冲突,导致编译出错。

Go Modoule

通过go.mod文件管理依赖包版本

通过go get/go mod指令工具管理依赖包

三要素:

1.配置文件,描述依赖:go.mod

2.中心仓库管理依赖库:Proxy

3.本地工具:go get/mod

7、Go依赖配置(go.mod)

image-20230201204024620

版本:

image-20230201204200863

image-20230201204223367

注:

1、主版本2+模块会在模块路径增加/vN后缀。 2、对于没有go.mod文件并且主版本2+的依赖,会+incompatible

8、工具 go.get/go.mod

image-20230201204622420

image-20230201204713423

三、实践练习例子:

1、协程使用:快速打印协程编号

代码:

 package main
 ​
 import (
    "fmt"
    "time"
 )
 ​
 func main() {
    for i := 0; i < 5; i++ {
       go func(j int) {
          holle(j)
       }(i)
    }
    time.Sleep(time.Second)
 }
 func holle(i int) {
    println("holle goroutine:" + fmt.Sprint(i))
 }

运行结果:

image-20230201180758150

可以看到确实是多个协程共同运行

2、协程通道使用

目的:

A子协程发送0~9数字

B子协程计算输入数字的平方

主协程输出最后的平方数

代码:

 //go:build ignore
 // +build ignore
 ​
 package main
 ​
 import "fmt"
 ​
 func main() {
    // 创建两个通道
    src := make(chan int)
    dest := make(chan int, 2)
    // 协程A
    go func() {
       defer close(src)
       for i := 0; i < 10; i++ {
          src <- i
       }
    }()
    // 协程B
    go func() {
       defer close(dest)
       for i := range src {
          dest <- i * i
       }
    }()
    // 主协程
    for i := range dest {
       fmt.Println(i)
    }
 }

运行结果:

image-20230201191144846

3、并发安全 Lock

目的:对变量执行2000次+1操作,5个协程并发执行

代码:

 package main
 ​
 import (
    "sync"
    "time"
 )
 ​
 func main() {
    add()
 }
 ​
 var (
    x    int64
    lock sync.Mutex
 )
 ​
 func addWithLock() {
    for i := 0; i < 2000; i++ {
       lock.Lock()
       x++
       lock.Unlock()
    }
 }
 ​
 func addWithoutLock() {
    for i := 0; i < 2000; i++ {
       x++
    }
 }
 func add() {
    x = 0
    for i := 0; i < 5; i++ {
       go addWithLock()
    }
    time.Sleep(time.Second)
    println("使用锁:", x)
    x = 0
    for i := 0; i < 5; i++ {
       go addWithoutLock()
    }
    time.Sleep(time.Second)
    println("不使用锁:", x)
 }

运行结果:

image-20230201193205915

可以看到使用了锁后,保证了共享变量的数据可见性。

4、语法熟悉——猜谜小游戏

目的:玩家随机猜测一个1~100的数,返回是否正确,不正确返回与目标数的大小关系。

代码:

 package main
 ​
 import (
    "bufio"
    "fmt"
    "math/rand"
    "os"
    "strconv"
    "strings"
    "time"
 )
 ​
 func main() {
    // 生成随机数
    maxNum := 100
    rand.Seed(time.Now().UnixNano())
    secretNum := rand.Intn(maxNum)
    // fmt.Println("最终答案:", secretNum)
    fmt.Println("请输入1~100中随机一个数:")
    count := 0
    for {
       // 输入猜测数
       reader := bufio.NewReader(os.Stdin)
       input, err := reader.ReadString('\n')
       if err != nil {
          fmt.Println("输入错误,请重试!!!")
          count++
          continue
       }
       input = strings.TrimSuffix(input, "\r\n")
       guess, err := strconv.Atoi(input)
       if err != nil {
          fmt.Println("不是整数,请重试!!!", err)
          count++
          continue
       }
       fmt.Println("你的猜测数:", guess)
       if guess < 1 && guess > 100 {
          fmt.Println("数字不在范围内,重新输入:")
          count++
          continue
       }
       // 判断是否正确
       count++
       if guess < secretNum {
          fmt.Println("数字小于答案,请再次尝试")
       } else if guess > secretNum {
          fmt.Println("数字大于答案,请再次尝试")
       } else {
          fmt.Println("恭喜你答对了!!!")
          fmt.Println("一共花费了:", count, "次")
          break
       }
    }
 ​
 }

测试:

 GOROOT=D:\Goland\go1.19.2 #gosetup
 GOPATH=C:\Users\lenovo\go #gosetup
 D:\Goland\go1.19.2\bin\go.exe build -o C:\Users\lenovo\AppData\Local\Temp\GoLand___go_build_TestDemo1_go.exe C:\Users\lenovo\go\src\awesomeProject\main\TestDemo1.go #gosetup
 C:\Users\lenovo\AppData\Local\Temp\GoLand___go_build_TestDemo1_go.exe
 请输入1~100中随机一个数:
 50
 你的猜测数: 50
 数字大于答案,请再次尝试
 25
 你的猜测数: 25
 数字小于答案,请再次尝试
 38
 你的猜测数: 38
 数字小于答案,请再次尝试
 44
 你的猜测数: 44
 数字大于答案,请再次尝试
 41
 你的猜测数: 41
 数字小于答案,请再次尝试
 42
 你的猜测数: 42
 数字小于答案,请再次尝试
 43
 你的猜测数: 43
 恭喜你答对了!!!
 一共花费了: 7 次
 ​
 进程 已完成,退出代码为 0

5、语法熟悉——彩云翻译数据爬取

目的:输入一个单词,爬取翻译的相关信息

代码:

 package main
 ​
 import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "strings"
 )
 ​
 type DictRequest struct {
    trans_type string
    source     string
 }
 ​
 func main() {
    client := &http.Client{}
    // 注意,"和`的混合使用
    var data = strings.NewReader(`{"trans_type": "en2zh","source": "good"}`)
    request, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
    if err != nil {
       log.Fatal(err)
    }
    request.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.35")
    request.Header.Set("content-type", "application/json;charset=UTF-8")
    request.Header.Set("origin", "https://fanyi.caiyunapp.com")
    request.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
    do, err := client.Do(request)
    if err != nil {
       log.Fatal(err)
    }
    defer do.Body.Close()
    text, err := ioutil.ReadAll(do.Body)
    if err != nil {
       log.Fatal(err)
    }
    fmt.Printf("%s\n", text)
 }

四、课后个人总结:

这节课熟悉了语法知识,写了很多go的代码。也了解了Go在并发并行方面的知识,Go的依赖管理方面的配置也明白了不少。