go工程进阶 | 青训营笔记

41 阅读4分钟

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

一、本堂课内容:

1. 并发编程

协程 Goroutine

协程是Go语言中的一种轻量级线程,它可以在一个线程中并发执行多个函数。使用协程可以大大提高程序的并发能力。

使用go关键字可以开启一个协程,如下所示:

package main

import "fmt"

func main() {
    go func() {
        fmt.Println("Hello, I'm a goroutine!")
    }()
    fmt.Println("Hello, I'm the main function!")
}

在上面的代码中,我们使用了go关键字来开启一个协程,该协程会在背后并发执行。我们还可以使用channel来进行协程间的通信,如下所示:

package main

import "fmt"

func main() {
    ch := make(chan int)
    go func() {
        ch <- 1
    }()
    fmt.Println(<-ch)
}

在上面的代码中,我们使用了channel来进行协程间的通信。

通道 Channel

通道是Go语言中用于进行协程间通信的重要工具。通道可以用于协程间的数据传递,也可以用于同步协程的执行。

通道可以使用make函数创建,还可以指定channel 的方向,如下所示:

Copy code
package main

import "fmt"

func main() {
    ch := make(chan int)
    go process(ch)

    for {
       select {
       case <-ch:
          fmt.Println("receive from channel")
          return
       default:
          fmt.Println("sleep 1 second")
       }
       time.Sleep(time.Second)
    }
   
}

func process(ch chan<- int) {
   defer func() {
      ch <- 1
   }()

   // do something
}

在上面的代码中,我们使用了make函数创建了一个int类型的通道,并使用<-运算符将数据写入通道和读取通道中的数据。

线程同步 WaitGroup

WaitGroup是Go语言中用于线程同步的重要工具。它可以用来等待多个线程完成后再继续执行。

使用方法如下:

package main

import (
    "fmt"
    "sync"
)

func main() {
    wg := sync.WaitGroup{}

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            fmt.Println("Hello, I'm a goroutine!")
            wg.Done()
        }()
    }
    wg.Wait()
    fmt.Println("All goroutines finished!")
}

在上面的代码中,我们使用了sync.WaitGroup来等待所有的协程完成后再继续执行。在每个协程执行前,我们都会使用wg.Add(1)来增加计数器,在每个协程执行结束后,我们都会使用wg.Done()来减少计数器。最后我们使用wg.Wait()来等待所有的协程完成,当计数器为0时,wg.Wait()函数才会返回。

锁 Mutex

锁是Go语言中用于保证线程安全的重要工具。锁可以用来保证在多个线程中对共享资源的互斥访问。

Go语言标准库中提供了sync包来支持锁的使用。其中最常用的锁是sync.Mutex。使用方法如下:

Copy code
package main

import (
    "fmt"
    "sync"
)

var counter int
var lock sync.Mutex

func main() {
    for i := 0; i < 10; i++ {
        go func() {
            lock.Lock()
            defer lock.Unlock()
            counter++
            fmt.Println(counter)
        }()
    }
}

在上面的代码中,我们使用了sync.Mutex来保证counter的并发安全。在每次访问counter之前,我们都会使用lock.Lock()来上锁,在访问结束后使用lock.Unlock()来解锁。

2. 依赖管理

直接使用 go module,gopath 默认的就行,如果有命令行工具的话,再把 gopath/bin 加到 PATH 里面

  1. Gopath

Gopath是Go语言中最早的依赖管理方式。它是使用的依赖管理方式是Gopath。Gopath是Go语言的工作空间目录,其中包含了第三方包和项目的源码。开发者可以手动将第三方包下载到Gopath目录中,然后在项目中使用import导入。

在Gopath中,代码的目录结构如下图所示:

- $GOPATH
  - src
    - github.com
      - user
        - project

其中,$GOPATH是环境变量,src目录下是所有的源码目录,github.com目录下是所有的第三方包目录。

  1. Go Vendor

Go Vendor是Go语言中比较新的依赖管理方式。它是通过在项目目录中创建vendor目录来管理依赖包。

在Go Vendor中,代码的目录结构如下图所示:

- project
  - vendor
    - github.com
      - user
        - project

其中,project目录是项目的根目录,vendor目录下是所有的依赖包目录。

  1. Go Module

Go Module 是Go语言中最新的依赖管理方式。Go Module 是一种基于版本的依赖管理工具,可以自动管理依赖的版本兼容性。开发者可以在项目中使用 go mod init 命令来初始化Go Module,然后使用 go get/mode 命令来管理依赖。

在Go Module中,代码的目录结构如下图所示:

- project
  - go.mod
  - go.sum

其中,project目录是项目的根目录,go.mod文件记录了项目的依赖信息,go.sum文件记录了项目依赖的校验和。

3. 单元测试

单元测试概念和规则

单元测试是指对于程序中的独立部分进行测试。在Go语言中,单元测试是通过testing包来实现的。

单元测试的规则如下:

  • 每个单元测试文件都必须以_test.go为后缀。
  • 每个单元测试函数都必须以Test开头。
  • 单元测试函数的第一个参数必须是testing.T类型。

示例代码如下:

package main

import "testing"

func TestAdd(t *testing.T) {
    result := add(1, 2)
    if result != 3 {
        t.Error("Expected 3, got", result)
    }
}

func add(a, b int) int {
    return a + b
}

基准测试

基准测试是指对于程序中的性能进行测试。在Go语言中,基准测试是通过testing包来实现的。

基准测试的规则如下:

  • 每个基准测试函数都必须以Benchmark开头。
  • 基准测试函数的第一个参数必须是*testing.B类型。

示例代码如下:

package main

import "testing"

func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        add(1, 2)
    }
}

func add(a, b int) int {
    return a + b
}

二、课后个人总结:

  1. Go语言中的并发编程的几个重要工具:协程、通道、锁和线程同步。这些工具可以帮助我们在多核处理器上提高程序的并发能力,并保证线程安全。
  2. 从Go语言1.0版本发布到现在,Go团队不断提供了新的工具来管理依赖。从最初的Gopath到后来的Go Vendor和Go Module,Go团队提供了更加方便、稳定和高效的依赖管理工具。现在,Go Module已经成为Go语言的官方依赖管理工具。
  3. 单元测试是对于程序中独立部分进行测试,可以保证程序的正确性。而基准测试是对于程序性能进行测试,可以保证程序的高效性。使用单元测试和基准测试可以保证程序的正确性和高效性。

三、引用参考:

books.studygolang.com/gopl-zh

www.bookstack.cn/books/the-w…