Golang入门 一.下载并配置环境 官网:所有版本 - Go 编程语言 Windows 下可以使用 .msi 后缀
将下载目录\Go\bin 目录添加到 Path 环境变量
选择GoLand IDE 开发 go语言,下载地址:JetBrains GoLand:不只是 Go IDE
第一个go程序 1 2 3 4 5 6 7 package mainimport "fmt" func main () { fmt.Println("Hello, World!" ) }
1.fmt 包实现了格式化 IO(输入/输出)的函数。
2.当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public );标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )
运行结果如下:
除了go run 执行命令,可以使用 go build 命令来生成可执行 二进制文件,和c语言一样。
二.数据类型和关键字 基本数据类型
数据类型
分类
描述
示例
bool
布尔型
布尔值,true或false
var b bool = true
string
字符串
字符串类型
var s string = "hello"
int
整型
平台相关的有符号整数(32或64位)
var i int = 42
int8
整型
8位有符号整数(-128到127)
var i int8 = 127
int16
整型
16位有符号整数(-32768到32767)
var i int16 = 32767
int32
整型
32位有符号整数(-2147483648到2147483647)
var i int32 = 2147483647
int64
整型
64位有符号整数
var i int64 = 9223372036854775807
uint
整型
平台相关的无符号整数(32或64位)
var u uint = 42
uint8
整型
8位无符号整数(0到255)
var u uint8 = 255
uint16
整型
16位无符号整数(0到65535)
var u uint16 = 65535
uint32
整型
32位无符号整数(0到4294967295)
var u uint32 = 4294967295
uint64
整型
64位无符号整数
var u uint64 = 18446744073709551615
uintptr
整型
用于存放指针的无符号整数
var p uintptr
byte
整型
uint8的别名
var b byte = 'A'
rune
整型
int32的别名,表示Unicode码点
var r rune = '中'
float32
浮点型
32位浮点数
var f float32 = 3.14
float64
浮点型
64位浮点数
var f float64 = 3.141592653589793
complex64
复数
实部和虚部都是float32的复数
var c complex64 = complex(1, 2)
complex128
复数
实部和虚部都是float64的复数
var c complex128 = complex(1, 2)
复合数据类型
数据类型
描述
示例
数组
固定长度的同类型元素序列
var a [3]int = [3]int{1, 2, 3}
切片
动态长度的同类型元素序列
var s []int = []int{1, 2, 3}
结构体
不同类型字段的组合
type Point struct { X, Y int }
指针
存储内存地址
var p *int
函数
函数类型
var f func(int) int
接口
方法集合
type Writer interface { Write([]byte) (int, error) }
映射
键值对集合
var m map[string]int = make(map[string]int)
通道
用于goroutine间通信
var ch chan int = make(chan int)
Go语言关键字
关键字
类别
描述
var
声明
声明变量
const
声明
声明常量
type
声明
声明类型
func
声明
声明函数
package
声明
声明包
import
声明
导入包
if
控制流
条件判断
else
控制流
条件判断
switch
控制流
多分支选择
case
控制流
switch的分支
default
控制流
switch的默认分支
for
控制流
循环
range
控制流
用于迭代数组、切片、映射或通道
break
控制流
退出循环或switch
continue
控制流
跳过当前循环迭代
goto
控制流
无条件跳转
fallthrough
控制流
在switch中继续执行下一个case
defer
控制流
延迟执行函数
return
控制流
从函数返回
go
并发
启动goroutine
chan
并发
声明通道
select
并发
多通道选择
interface
类型
声明接口
struct
类型
声明结构体
map
类型
声明映射类型
预声明标识符
标识符
描述
true, false
布尔常量
iota
常量生成器
nil
指针/引用类型的零值
append
向切片追加元素
cap
获取容量
close
关闭通道
complex
构造复数
copy
复制切片
delete
删除映射元素
imag
获取复数虚部
len
获取长度
make
分配并初始化slice/map/chan
new
分配内存
panic
运行时错误
print, println
打印输出
real
获取复数实部
recover
处理panic
三.基础语法 变量声明和使用 1 2 3 4 5 6 7 8 9 10 var a int = 100 var b = 200 c := 300 var ( vname1 v_type1 vname2 v_type2 ) g, h := 123 , "hello"
另:局部变量 没有在其代码块中使用,会编译错误
输入输出 Go 语言中使用 fmt.Sprintf 或 fmt.Printf 格式化字符串并赋值给新串:
Sprintf 根据格式化参数生成格式化的字符串并返回该字符串。
Printf 根据格式化参数生成格式化的字符串并写入标准输出。
1 2 3 4 5 6 7 8 9 var stockcode = 123 var enddate = "2020-12-31" var url = "Code=%d&endDate=%s" sprintf := fmt.Srintf(url, stockcode, enddate) fmt.Scanf("%d%d" ,&num1,&num2) fmt.Println(num1,num2)
值类型和引用类型 int、float、bool 和 string 这些基本类型都属于值类型,这些类型的变量直接指向存在内存中的值
当使用等号 = 将一个值类型变量的值赋值给另一个值类型变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝,通过 &i 来获取变量 i 的内存地址,这个内存地址称之为指针 ,这个指针实际上也被存在另外的某一个值中。
空白标识符用于占位 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "fmt" func main () { _,numb,strs := numbers() fmt.Println(numb,strs) } func numbers () (int ,int ,string ){ a , b , c := 1 , 2 , "str" return a,b,c }
常量 在程序运行时,不会被修改的量
1 2 3 4 5 6 7 const a = "abc" const ( Unknown = 0 Female = 1 Male )
常量可以用len(), cap(), unsafe.Sizeof()函数计算表达式的值
iota iota,特殊常量,可以认为是一个可以被编译器修改的常量。
定义一组枚举值时,可以简化代码
iota 在 新const关键字出现时将被重置为 0
1 2 3 4 5 6 7 8 const ( a = iota b c d = "ha" e f = 100 )
运算符 略 与其他语言基本一致
指针变量 ***** 和地址值 & 的区别 1 2 3 4 5 6 7 8 func main () { var a int = 4 var ptr *int ptr = &a println ("a的值为" , a); println ("*ptr为" , *ptr); println ("ptr为" , ptr); }
条件语句 除了 if..else switch,go独有select 语句
select 是 Go 中的一个控制结构,类似于 switch 语句。
select 语句只能用于通道操作 ,每个 case 必须是一个通道操作,要么是发送要么是接收。
select 语句会监听所有指定的通道上的操作,一旦其中一个通道准备好就会执行相应的代码块。
如果多个通道都准备好,那么 select 语句会随机选择一个通道执行。如果所有通道都没有准备好,那么执行 default 块中的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 select { case <- channel1: case value := <- channel2: case channel3 <- value: default : }
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport ( "fmt" "time" ) func main () { c1 := make (chan string ) c2 := make (chan string ) go func () { time.Sleep(1 * time.Second) c1 <- "one" }() go func () { time.Sleep(2 * time.Second) c2 <- "two" }() for i := 0 ; i < 2 ; i++ { select { case msg1 := <-c1: fmt.Println("received" , msg1) case msg2 := <-c2: fmt.Println("received" , msg2) } } }
循环语句 1 2 3 4 5 6 7 8 9 10 11 12 for true { fmt.Printf("这是无限循环。\n" ); continue ; break ; } LOOP: for a < 20 { if a == 15 { a = a + 1 goto LOOP }
函数 Go 语言函数定义格式如下
1 2 3 func function_name ( [parameter list] ) [return_types] { 函数体 }
Go 函数可以返回多个值
函数可以作为参数——函数参数
1 2 3 4 5 6 7 getSquareRoot := func (x float64 ) float64 { return math.Sqrt(x) } fmt.Println(getSquareRoot(9 ))
Go 语言函数闭包(匿名函数) 匿名函数是一种没有函数名的函数,通常用于在函数内部定义函数,或者作为函数参数进行传递。
匿名函数是一个”内联”语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package mainimport "fmt" func getSequence () func () int { i:=0 return func () int { i+=1 return i } } func main () { nextNumber := getSequence() fmt.Println(nextNumber()) fmt.Println(nextNumber()) fmt.Println(nextNumber()) nextNumber1 := getSequence() fmt.Println(nextNumber1()) fmt.Println(nextNumber1()) }
Go 语言函数方法 Go 语言中同时有函数和方法。一个方法就是一个包含了接受者的函数,接受者 可以是命名类型或者结构体类型的一个值或者是一个指针。
“接受者” (Receiver)指的是定义在方法上的那个类型的变量,也就是调用方法的对象。它类似于其他面向对象语言中的“实例”或“对象”。
1 2 3 func (variable_name variable_data_type) function_name() [return_type]{ }
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport ( "fmt" ) type Circle struct { radius float64 } func main () { var c1 Circle c1.radius = 10.00 fmt.Println("圆的面积 = " , c1.getArea()) } func (c Circle) getArea() float64 { return 3.14 * c.radius * c.radius }
数组 在 Go 语言中,数组的大小是类型的一部分,因此不同大小的数组是不兼容的
定义数组 1 2 3 4 5 6 7 balance := [5 ]float32 {1000.0 , 2.0 , 3.4 , 7.0 , 50.0 } balance := [...]float32 {1000.0 , 2.0 , 3.4 , 7.0 , 50.0 } balance := [5 ]float32 {0 :2.0 ,1 :7.0 ,3.0 } var a = [3 ][5 ]int {{1 , 2 , 3 , 4 , 5 }, {0 , 9 , 8 , 7 , 6 }, {3 , 4 , 5 , 6 , 7 }}
获取数组元素
Go 指针 与C语言一致
当一个指针被定义后没有分配到任何变量时,它的值为 nil ,也称为空指针。
nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。
一个指针变量通常缩写为 ptr。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func main () { var a int = 100 var b int = 200 swap(&a, &b); fmt.Printf("交换后 a 的值 : %d\n" , a ) fmt.Printf("交换后 b 的值 : %d\n" , b ) } func swap (x *int , y *int ) { *x, *y = *y, *x } a, b = b, a
结构体 结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体中有一个或多个成员。type 语句设定了结构体的名称。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Student struct { name string age int grade int sex string score float64 } func main () { student1 := Student{age: 12 , grade: 3 , sex: "Male" , score: 100.0 } student1.name = "Tom" student2 := Student{"Alex" , 15 , 2 , "Female" , 90.0 } fmt.Println(student1) fmt.Println(student2) }
输出:
1 2 {Tom 12 3 Male 100} {Alex 15 2 Female 90}
Go 语言没有传统面向对象语言中的类(class)和继承(inheritance)概念,而是通过组合(composition)和接口(interface)来实现类似的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package mainimport "fmt" type Animal struct { Name string } func (a *Animal) Speak() { fmt.Println(a.Name, "says hello!" ) } type Dog struct { Animal Breed string } func main () { dog := Dog{ Animal: Animal{Name: "Buddy" }, Breed: "Golden Retriever" , } dog.Speak() fmt.Println("Breed:" , dog.Breed) }
Go 语言切片(Slice) Go 语言切片是对数组的抽象,相当于其他语言的动态数组 。
定义切片 1 2 3 4 5 make ([]T, length, capacity) var numbers []int slice1 := make ([]int , 5 )
初始化切片 1 2 3 4 5 6 7 8 9 10 11 12 13 s :=[] int {1 ,2 ,3 } s[1 ] = 1 arr := [5 ]int {1 , 2 , 3 , 4 , 5 } slice2 := arr[:] s := arr[startIndex:endIndex] s := arr[startIndex:] s := arr[:endIndex] numbers = append (numbers, 1 ) copy (numbers1,numbers)
范围(Range) range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "fmt" var pow = []int {1 , 2 , 4 , 8 , 16 , 32 , 64 , 128 }func main () { for i, v := range pow { fmt.Printf("2**%d = %d\n" , i, v) } }
Go 语言Map(集合) Map 是一种无序的键值对的集合。
定义,初始化Map 可以使用内建函数 make 或使用 map 关键字来定义 Map:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 map_variable := make (map [KeyType]ValueType, initialCapacity) m := map [string ]int { "apple" : 1 , "banana" : 2 , "orange" : 3 , } v1 := m["apple" ] v2, ok := m["pear" ] delete (m, "banana" )for k, v := range m { fmt.Printf("key=%s, value=%d\n" , k, v) }
类型转换 go 不支持隐式转换类型,一般在前面直接加要转换的类型:
1 2 3 4 5 var sum int = 17 var count int = 5 var mean float32 mean = float32 (sum)/float32 (count) fmt.Printf("mean 的值为: %f\n" ,mean)
字符串类型转换 将一个字符串转换成另一个类型,可以使用以下语法:
1 2 3 var str string = "10" var num int num, _ = strconv.Atoi(str)
strconv.Atoi 函数返回两个值,第一个是转换后的整型值,第二个是可能发生的错误,可以使用空白标识符 _ 来忽略这个错误
Go 语言接口 接口(interface)是 Go 语言中的一种类型,用于定义行为的集合,它通过描述类型必须实现的方法,规定了类型的行为契约。
接口定义和实现 接口定义使用关键字 interface ,其中包含方法声明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type Shape interface { Area() float64 Perimeter() float64 } type Circle struct { Radius float64 } func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius } func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius } func main () { c := Circle{Radius: 5 } var s Shape = c fmt.Println("Area:" , s.Area()) fmt.Println("Perimeter:" , s.Perimeter()) }
错误处理 Go 语言的错误处理采用显式返回错误的方式,而非传统的异常处理机制。这种设计使代码逻辑更清晰,便于开发者在编译时或运行时明确处理错误。
error 接口 Go 标准库定义了一个 error 接口,表示一个错误的抽象。
error 类型是一个接口类型,这是它的定义:
1 2 3 type error interface { Error() string }
实现 error 接口 :任何实现了 方法的类型都可以作为错误。Error()
1 2 3 4 func main () { err := errors.New("this is an error" ) fmt.Println(err) }
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func divide (a, b int ) (int , error ) { if b == 0 { return 0 , errors.New("division by zero" ) } return a / b, nil } func main () { result, err := divide(10 , 0 ) if err != nil { fmt.Println("Error:" , err) } else { fmt.Println("Result:" , result) } }
Errorf函数打印错误信息 1 2 error := fmt.Errorf("this is an error" )fmt.Println(error )
底层就是调用了error.New()
中断程序 panic 函数,发生异常就终止程序运行
1 2 3 4 5 6 7 8 9 10 11 12 func main () { fmt.Println(div(10 , 0 )) } func div (a int , b int ) (result int ) { if b == 0 { panic ("除数不能为0" ) fmt.Println("我运行了吗" ) } result = a / b return result }
恢复程序 defer 和recover 来实现panic 捕获的异常,让程序继续执行
被 defer 修饰的函数会在当前函数返回之前执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func div (a, b int ) (result int ) { defer func () { if error := recover (); error != nil { result = -1 fmt.Println(error ) } }() if b == 0 { panic ("除数不能为0" ) panic ("未知错误" ) } return a / b } func main () { fmt.Println(div(10 , 0 )) }
输出:
字符串操作 Go语言的编码方式是UTF-8 ,汉字占3个字节
如果字符串包括中文等,想计算字符的个数而不是字节数,需将字符串转换成rune 类型数组
rune 相当Java的char
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 var a = "123你好バカヤロー" func main () { arr1 := []byte (a) for _, v := range arr1 { fmt.Print(v) fmt.Printf("\t" ) fmt.Printf(string (v)) fmt.Printf("\t" ) fmt.Printf("%c\n" , v) } fmt.Println(len (arr1)) arr2 := []rune (a) for _, v := range arr2 { fmt.Print(v) fmt.Printf("\t" ) fmt.Printf("%c\n" , v) } fmt.Println(len (arr2)) }
查找子串在字符串中出现的位置
1 2 3 4 5 6 7 8 9 func Index (s, sep string ) int func IndexByte (s string , c byte ) int func IndexRune (s string ,r rune ) int func IndexAny (s, chars string ) int func IndexFunc (s string , f func (rune ) bool ) int func LastIndex (s, sep string ) int func LastIndexByte (s string , c byte ) int func LastIndexAny (s, chars string ) int func LastIndexFunc (s string , f func (rune ) bool ) int
判断字符串中是否包含子串
1 2 3 4 5 func Contains (s, substr string ) bool func ContainsRune (s string ,r rune ) bool func ContainsAny (s, chars string ) bool func HasPrefix (s, prefix string ) bool func HasSuffix (s, suffix string ) bool
…
正则表达式 在 Go 语言中,正则表达式通过 regexp 包来实现。
常用函数
Compile 和 MustCompile 用于编译正则表达式。Compile 返回一个 *Regexp 对象和一个错误,而 MustCompile 在编译失败时会直接 panic。使用 Compile 函数时,务必检查返回的错误,以避免程序崩溃。
MatchString 检查字符串是否匹配正则表达式。
FindString 和 FindAllString 用于查找匹配的字符串。FindString 返回第一个匹配项,FindAllString 返回所有匹配项。
ReplaceAllString 用于替换匹配的字符串。
Split 根据正则表达式分割字符串。
常用正则表达式语法 1 2 3 4 5 6 7 8 9 10 .:匹配任意单个字符(除了换行符)。 *:匹配前面的字符 0 次或多次。 +:匹配前面的字符 1 次或多次。 ?:匹配前面的字符 0 次或 1 次。 \d:匹配数字字符(等价于 [0-9])。 \w:匹配字母、数字或下划线(等价于 [a-zA-Z0-9_])。 \s:匹配空白字符(包括空格、制表符、换行符等)。 []:匹配括号内的任意一个字符(例如 [abc] 匹配 a、b 或 c)。 ^:匹配字符串的开头。 $:匹配字符串的结尾。
示例 1 2 3 4 5 6 7 8 9 10 11 12 func main () { pattern := `^[a-zA-Z0-9\.]+$` regex := regexp.MustCompile(pattern) reader := bufio.NewReader(os.Stdin) input, _ := reader.ReadString('\n' ) input = strings.TrimSpace(input) if regex.MatchString(input) { fmt.Println("密码格式正确" ) } else { fmt.Println("密码格式错误" ) } }
1 2 3 regex := regexp.MustCompile(`[a-zA-Z]+` ) result := regex.FindAllString("abc123def456" , -1 )
时间和日期函数 1 2 3 4 5 6 7 8 t := time.Now() fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second()) fmt.Println(t.Format("2006.01.02 15:04:05" )) time.Sleep(time.Second * 2 ) fmt.Println(t.Unix())
类型断言 在 Go 语言中,类型断言(Type Assertion)是一种用于检查接口值的实际类型的机制。
类型断言是 Go 语言中处理接口类型的重要工具,它允许我们从接口值中提取出具体的类型,并对其进行操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var i interface {} = "Hello, Go!" if s, ok := i.(string ); ok { fmt.Println("处理字符串:" , s) } else if n, ok := i.(int ); ok { fmt.Println("处理整数:" , n) } else { fmt.Println("无法处理的类型" ) } m := i.(int ) fmt.Println("断言成功:" , n) }
Go 还提供了特殊的 type switch 语法来测试多种类型:
1 2 3 4 5 6 7 8 9 10 func printType (i interface {}) { switch v := i.(type ) { case int : fmt.Println("这是一个整数:" , v) case string : fmt.Println("这是一个字符串:" , v) default : fmt.Println("未知类型" ) } }
文件操作 Go 语言提供了丰富的标准库来支持文件处理,包括文件的打开、关闭、读取、写入、追加和删除等操作。
os 是核心库 :提供底层文件操作(创建、读写、删除等),大多数场景优先使用。
io 提供通用接口 :如 Reader/Writer,可与文件、网络等数据源交互。
bufio 优化性能 :通过缓冲减少 I/O 操作次数,适合频繁读写。
创建并写入文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func createAddWriteFile () { file, err := os.Create("123.txt" ) if err != nil { log.Fatal(err) } bytes := []byte {'1' , 'b' , '\r' , '\n' , '4' } file.Write(bytes) file.WriteString("\r\nHello World" ) w := bufio.NewWriter(file) w.WriteString("こにちは" ) w.Flush() defer file.Close() }
打开并读入文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 func openAndReadFile () { file, err := os.Open("123.txt" ) file, err = os.OpenFile("123.txt" , os.O_RDWR, 0666 ) if err != nil { log.Fatal(err) } buf := make ([]byte , 10 ) for { count, err := file.Read(buf) if err != nil && err != io.EOF { log.Fatal(err) } fmt.Println(buf[:count]) if err == io.EOF { break } } _, err = file.Seek(0 , 0 ) if err != nil { log.Fatal(err) } r := bufio.NewReader(file) for { readString, err := r.ReadString('\n' ) fmt.Println(readString) if err == io.EOF { break } } defer file.Close() }
OpenFile打开模式 1 2 3 4 5 6 7 8 9 10 11 const ( // Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified. O_RDONLY int = syscall.O_RDONLY // open the file read-only. O_WRONLY int = syscall.O_WRONLY // open the file write-only. O_RDWR int = syscall.O_RDWR // open the file read-write. O_APPEND int = syscall.O_APPEND // append data to the file when writing. O_CREATE int = syscall.O_CREAT // create a new file if none exists. O_EXCL int = syscall.O_EXCL // used with O_CREATE, file must not exist. O_SYNC int = syscall.O_SYNC // open for synchronous I/O. O_TRUNC int = syscall.O_TRUNC // truncate regular writable file when opened. )
打开权限 0.没有任何权限 1.执行权限(如果是可执行程序,可以运行) 2.写权限 3.写权限和执行权限 4.读权限 5.读权限和执行权限 6.读权限和写权限 7.读权限和写权限以及执行权限
文件是否存在 1 2 3 4 5 6 7 8 9 10 11 12 func isExist () { _, err := os.Stat("123.txt" ) if err != nil { if os.IsExist(err) { fmt.Println("文件已存在" ) } else if os.IsNotExist(err){ fmt.Println("文件不存在" ) }else { fmt.Println("文件状态未知" ) } } }
Go 并发 并发是指程序同时执行多个任务的能力。
Go 语言支持并发,通过 goroutines 和 channels 提供了一种简洁且高效的方式来实现并发。
Goroutines:
Go 中的并发执行单位,类似于轻量级的线程。
Goroutine 的调度由 Go 运行时管理,用户无需手动分配线程。
使用 go 关键字启动 Goroutine。
Goroutine 是非阻塞的,可以高效地运行成千上万个 Goroutine。
Channel:
Go 中用于在 Goroutine 之间通信的机制。
支持同步和数据共享,避免了显式的锁机制。
使用 chan 关键字创建,通过 <- 操作符发送和接收数据。
Scheduler(调度器):
Go 的调度器基于 GMP 模型,调度器会将 Goroutine 分配到系统线程中执行,并通过 M 和 P 的配合高效管理并发。
goroutine 语法格式
通道(Channel) 使用 make 函数创建一个 channel,使用 <- 操作符发送和接收数据。如果未指定方向,则为双向通道。
1 2 3 4 5 6 7 8 9 10 11 12 13 ch := make (chan int ) ch := make (chan int , 100 ) ch <- v v := <-ch close (ch)go func (c chan int ) { go func (c <- chan int ) { go func (c chan <- int ) {
实例 通过两个 goroutine 来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport "fmt" func sum (s []int , c chan int ) { sum := 0 for _, v := range s { sum += v } c <- sum } func main () { s := []int {7 , 2 , 8 , -9 , 4 , 0 } c := make (chan int ) go sum(s[:len (s)/2 ], c) go sum(s[len (s)/2 :], c) x, y := <-c, <-c close (c) fmt.Println(x, y, x+y) }
使用select语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package mainimport "fmt" func fibonacci (c, quit chan int ) { x, y := 0 , 1 for { select { case c <- x: x, y = y, x+y case <-quit: fmt.Println("quit" ) return } } } func main () { c := make (chan int ) quit := make (chan int ) go func () { for i := 0 ; i < 10 ; i++ { fmt.Println(<-c) } quit <- 0 }() fibonacci(c, quit) }
使用 WaitGroup sync.WaitGroup 用于等待多个 Goroutine 完成。
同步多个 Goroutine:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport ( "fmt" "sync" ) func worker (id int , wg *sync.WaitGroup) { defer wg.Done() fmt.Printf("Worker %d started\n" , id) fmt.Printf("Worker %d finished\n" , id) } func main () { var wg sync.WaitGroup for i := 1 ; i <= 3 ; i++ { wg.Add(1 ) go worker(i, &wg) } wg.Wait() fmt.Println("All workers done" ) }
其他高级特性 Context:
用于控制 Goroutine 的生命周期。
1 context.WithCancel、context.WithTimeout。
Mutex 和 RWMutex:
sync.Mutex 提供互斥锁,用于保护共享资源。
1 2 3 4 var mu sync.Mutexmu.Lock() mu.Unlock()
Continued…
后面会持续补充