Go 语言入门指南:基础语法和常用特性解析 | 青训营

43 阅读5分钟

1.语法基础

1.1 用户例程

func hello(i int) {
	println("hello :" + fmt.Sprint(i))
}

func HelloGoRoutine() {
	for i := 0; i < 5; i++ {
		go func(j int) {
			hello(j)
		}(i)
	}
	time.Sleep(time.Second)
}

这是一个使用 Goroutine 的例程。Goroutine 是 Go 语言中的轻量级线程,可以与其他 Goroutine 并发执行这些函数可以并发执行,而不是顺序执行。

1.2 CSP

CSP(Communicating Sequential Processes)是一种并发编程模型,它提倡通过通信来共享内存,而不是通过共享内存来实现通信。在 Go 语言中,CSP 是通过使用通道(Channel)来实现的。

1.3 Channel

在 Go 语言中,通道(Channel)用于在 Goroutine 之间进行通信。通道可以用来发送和接收值,确保并发操作的安全性。

通过 make 函数可以创建一个通道,其语法如下:

make(chan 元素类型, [缓冲大小])

  • 无缓冲通道:make(chan int)
  • 有缓冲通道:make(chan int, 2)

使用通道可以使操作有序执行,通过发送和接收操作的配对,可以确保 Goroutine 之间的同步。

1.4 并发安全 lock

在并发编程中,当多个 Goroutine 同时访问共享资源时,可能会发生竞态条件(Race Condition)导致程序出现错误。为了避免这种情况,可以使用锁机制来保护共享资源,确保同一时间只有一个 Goroutine 可以访问。

在 Go 语言中,可以使用 sync.Mutex(互斥锁)来实现锁机制。以下是一个使用锁的示例代码:

func addWithLock() {
	for i := 0; i < 2000; i++ {
		lock.Lock()
		x += 1
		lock.Unlock()
	}
}
func addWithoutLock() {
	for i := 0; i < 2000; i++ {
		x += 1
	}
}
func Add() {
	x = 0
	for i := 0; i < 5; i++ {
		go addWithoutLock()
	}
	time.Sleep(time.Second)
	x = 0
	for i := 0; i < 5; i++ {
		go addWithLock()
	}
}

上述代码定义了一个全局变量 x 和一个互斥锁 lock。addWithLock 函数在执行时会先锁定互斥锁,然后对 x 进行加一操作,最后释放锁。而 addWithoutLock 函数没有使用锁。在 Add 函数中,首先启动了 5 个并发执行的addWithoutLock Goroutine,接着等待一段时间后,再启动了 5 个并发执行的 addWithLock Goroutine。通过比较加锁和不加锁的结果,可以观察到加锁可以确保共享资源的正确操作。

应尽量避免对共享内存的直接操作,而是通过锁或通道等机制来实现并发安全。

2.依赖

2.1 依赖管理

在 Go 语言中,依赖管理的方式从早期的 GOPATH 切换到了 Go Module。

2.1.1 GOPATH

在 GOPATH 中,项目的源码、编译中间产物和二进制文件都存放在不同的目录中:

  • src 目录用于存放项目源码;
  • pkg 目录用于存放项目编译的中间产物,可以加速编译过程;
  • bin 目录用于存放编译的二进制文件。

2.1.2 Go Vendor

Go Vendor 是一种依赖管理的方式,每个项目引入的依赖会被拷贝到项目的 Vendor 目录中。这样每个项目都有自己的依赖副本,可以独立管理和更新。

2.1.3 Go Module

Go Module 是 Go 1.11 版本引入的官方依赖管理工具。它通过配置文件 go.mod 来描述项目的依赖关系,并且使用中心仓库 Proxy 来管理依赖库。

Go Module 提供了一系列命令和功能,可以方便地进行依赖管理,包括 go getgo mod initgo mod downloadgo mod tidy 等。

2.3 配置

2.3.2 依赖配置

在 Go Module 中,依赖的版本号使用语义化版本(Semantic Versioning)进行配置。版本号的格式为 MAJOR.{MAJOR}.{MINOR}.${PATCH},不同主版本号(MAJOR)之间的版本通常是不兼容的,因此在配置依赖时可以隔离不同主版本之间的兼容性。

2.3.3 indirect

在依赖配置中,indirect 表示该依赖是间接依赖,而不是直接使用的依赖。这些间接依赖可能是由其他依赖引入的,但是对于当前项目来说并不是直接使用的。

2.3.4 选择满足本次构建的最低兼容版本

在构建过程中,Go Module 会选择满足本次构建所需的最低兼容版本。这样可以确保项目在构建时使用的依赖版本是兼容的。如果项目的依赖关系发生变化,可以使用 go mod tidy 命令来更新依赖。

2.3.7 go get

go get 是 Go Module 提供的一个命令,用于获取远程代码并将其加入到本地缓存中。它可以用于下载其他项目的依赖,也可以用于安装命令行工具等。

go get 命令支持一些选项,例如 @none 可以用于删除指定版本的依赖,-u 可以用于更新已有的依赖。

另外,还有一些其他的 go mod 命令用于初始化、下载依赖和整理依赖等操作,例如 go mod init、go mod download 和 go mod tidy。

3 测试

在软件开发中,测试是保证代码质量的重要手段。在 Go 语言中,测试可以分为多个层次,包括单元测试、集成测试和回归测试等。

3.1 单元测试

单元测试是对代码中最小可测试单元的测试,通常是对函数、方法或类型进行测试。在 Go 语言中,单元测试的文件名以 _test.go 结尾,并且测试函数的签名为 func TestXxxx(*testing.T)。

以下是一些关于单元测试的规则:

  • 所有的测试文件名必须以_test.go 结尾;
  • 测试函数的签名必须为 func TestXxxx(*testing.T);
  • 初始化逻辑可以放在 TestMain 函数中,该函数会在测试开始前执行;
  • 可以使用t.Error 或 t.Fail

4 个人感想

在我看来,Go语言的并发编程模型非常强大且易于使用。通过Goroutine和Channel的组合,可以轻松地实现并发执行和协作通信。Goroutine的轻量级特性使得创建和管理大量并发任务变得简单,而Channel的存在可以有效地在Goroutine之间传递数据,确保并发操作的安全性和同步性。