Go 基础
Go 特点:简单,高效,并发友好。
安装
Go 官网:https://golang.google.cn/
安装方法参考:Download and install
# 先从官网下载 Go
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.25.5.linux-amd64.tar.gz
# 设置环境变量
export PATH=$PATH:/usr/local/go/bin
# 查看版本
go version
学习资料
Golang 中文学习文档:https://golang.halfiisland.com/
工程创建
# 创建项目目录
mkdir mydata
cd mydata
# 初始化项目
go mod init mydata
# 创建 main.go 文件
touch main.go
# 编写代码
vim main.go
示例代码:
package main // 包名
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
清理 go.mod 中的无用依赖项:
go mod tidy
编译:
go build main.go
运行:
go run main.go
Go 基础
包
在 Go 中,程序是通过将包链接在一起来构建的。Go 中进行导入的最基本单位是一个包,而不是 .go 文件。包其实就是一个文件夹,英文名 package,包内共享所有变量、常量,以及所有定义的类型。包的命名风格建议都是小写字母,并且要尽量简短。package 关键字声明了当前 go 文件属于哪一个包。
cmd # cmd 包,文件夹名即为包名
--> a.go # package cmd
--> b.go # package cmd
包内共享所有变量、常量,以及所有定义的类型,但对于包外而言并不是这样,有时候你并不想让别人访问某一个类型,所以就需要控制可见性。包的可见性规则:
- 名称大写字母开头,即为公有类型/变量/常量
- 名字小写或下划线开头,即为私有类型/变量/常量
导入一个包来使用这个包中的类型/方法/函数/变量,导入的语法就是 import 加上包名:
import "fmt"
Go 中约定,一个包内名为 internal 包为内部包,外部包将无法访问内部包中的任何内容,否则的话编译不通过。
函数
函数声明:
func 函数名([参数列表]) [返回值] {
函数体
}
变量声明,var 变量名 类型名:
var intNum int
指针:Go 保留了指针,在一定程度上保证了性能,同时为了更好的 GC 和安全考虑,又限制了指针的使用。关于指针有两个常用的操作符,一个是取地址符 &,另一个是解引用符 *。
延迟调用:defer 关键字可以使得一个函数延迟一段时间调用,在函数返回之前这些 defer 描述的函数最后都会被逐个执行。
func main() {
Do()
}
func Do() {
defer func() {
fmt.Println("1") // 最后执行
}()
fmt.Println("2") // 先执行
}
init 函数是一个特殊的初始化函数,用于在程序启动时执行包级别的初始化逻辑。它无需手动调用,由 Go 运行时自动执行。
并发
Go 语言对于并发的支持是纯天然的,这是这门语言的核心所在,其上手难度相对较小,开发人员不太需要关注底层实现就能做出一个相当不错的并发应用,提高了开发人员的下限。
协程:协程(coroutine)是一种轻量级的线程,或者说是用户态的线程,不受操作系统直接调度,由 Go 语言自身的调度器进行运行时调度,因此上下文切换开销非常小,这也是为什么 Go 的并发性能很不错的原因之一。
在 Go 中,创建一个协程十分的简单,仅需要一个 go 关键字,就能够快速开启一个协程,go 关键字后面必须是一个函数调用。
func main() {
go fmt.Println("hello world!")
go hello()
go func() {
fmt.Println("hello world!")
}()
}
func hello() {
fmt.Println("hello world!")
}
Go 提供了非常多的并发控制手段,常用的并发控制方法有三种:
- channel:管道
- WaitGroup:信号量
- Context:上下文 三种方法有着不同的适用情况,WaitGroup 可以动态的控制一组指定数量的协程,Context 更适合子孙协程嵌套层级更深的情况,管道更适合协程间通信。对于较为传统的锁控制,Go 也对此提供了支持:
- Mutex:互斥锁
- RWMutex:读写互斥锁
channel:管道,即通过消息来进行内存共享,channel 就是为此而生,它是一种在协程间通信的解决方案。
在创建管道时,有且只有一种方法,那就是使用内置函数 make,对于管道而言,make 函数接收两个参数,第一个是管道的类型,第二个是可选参数为管道的缓冲大小。例子如下:
intCh := make(chan int)
// 缓冲区大小为 1 的管道
strCh := make(chan string, 1)
在使用完一个管道后一定要记得关闭该管道,使用内置函数 close 来关闭一个管道,该函数签名如下。
func close(c chan<- Type)
func main() {
intCh := make(chan int) // 创建管道
// do something
close(intCh) // 关闭管道
}
对于一个管道而言,Go 使用了两种很形象的操作符来表示读写操作:
ch <- 数据 // 表示对一个管道写入数据
变量 := <-ch // 表示对一个管道读取数据
示例:
func main() {
// 创建无缓冲管道
ch := make(chan int)
defer close(ch)
go func() {
// 写入数据
ch <- 123
}()
// 读取数据
n := <-ch
fmt.Println(n)
}
管道中的数据流动方式与队列一样,即先进先出(FIFO),协程对于管道的操作是同步的,在某一个时刻,只有一个协程能够对其写入数据,同时也只有一个协程能够读取管道中的数据。
参考书: Go 语言设计与实现 Go 浅析