Go语言进阶和依赖管理(二)——并发和依赖管理

一、本文重点内容:

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

    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的依赖管理方面的配置也明白了不少。

猜你喜欢

转载自blog.csdn.net/qq_54353206/article/details/128840920
今日推荐