基本语法 HelloWorld
简单示例1 2 3 4 package mainfunc main () { fmt.Println("Hello World!" ) }
终端运行
1 2 $ go run helloworld.go Hello World
go run
表示直接运行 直接编译 go 语言并执行应用程序,一步完成 也可以先 go build helloworld.go || ./helloworld.go
build
成静态语言然后在执行
go 语言的语言, 写出规范的一致代码是很重要的 比如写函数的时候’{‘ 必须和函数名在同一行 建议使用 go 的默认格式化程序来约束
上面的执行流程是
package main : 定义了包名 必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main 表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包import “fmt” : 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。func main() : main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)fmt.Println(…) : 可以将字符串输出到控制台,并在最后自动增加换行字符 \n。 使用 fmt.Print(“hello, world\n”) 可以得到相同的结果。 Print 和 Println 这两个函数也支持使用变量,如:fmt.Println(arr)。如果没有特别指定,它们会以默认的打印格式将变量 arr 输出到控制台。变量声明 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" var x, y int var ( a int = 1 b bool = true ) var c, d int = 1 ,2 var e, f = 123 , "yueyun" func main () { g,h := 123 , "explosion" _,t = nil , 'Explosion' fmt.Println(g,h.t) }
声明⼀个变量 默认的值是 0 var a int
声明⼀个变量,初始化⼀个值 var b int = 100
在初始化的时候,可以省去数据类型,通过值⾃动匹配当前的变量的数据类型 var c = 100
(常⽤的⽅法) 省去 var 关键字,直接⾃动匹配 e := 100
(不支持全局)
常量 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 package mainimport "fmt" func main () { const a int = 10 const ( b int = 20 c string = "Hello" ) const ( BEJing = 10 * iota SHANGHAI SHENZHEN ) const ( _ = iota KB ByteSize = 1 << (10 * iota ) MB GB TB PB EB ZB YB ) fmt.Println(a, b, c, BEJing, SHANGHAI, SHENZHEN) }
函数 基本示例 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 package mainimport "fmt" func foo1 () { fmt.Println("foo1" ) } func foo2 (a int , b string ) (int , string ) { fmt.Println("foo2" , a, b) return a + 1 , b + "explosion" } func foo3 (a int , b string ) (c int , d string ) { fmt.Println("foo3" , a, b, c, d) c = a + 1 d = b + "explosion" return } func main () { foo1() foo2(1 , "hello" ) foo3(2 , "world" ) }
函数传参 函数传递参数包括值传递和引用传递
这里首先介绍一下指针
值传递 值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。
以下定义了 swap() 函数:
1 2 3 4 5 6 7 8 9 10 11 12 func swap (x, y int ) int { var temp int temp = x x = y y = temp return temp; }
接下来,让我们使用值传递来调用 swap() 函数:
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 35 36 37 38 39 40 41 42 43 44 package mainimport "fmt" func main () { var a int = 100 var b int = 200 fmt.Printf("交换前 a 的值为 : %d\n" , a ) fmt.Printf("交换前 b 的值为 : %d\n" , b ) swap(a, b) fmt.Printf("交换后 a 的值 : %d\n" , a ) fmt.Printf("交换后 b 的值 : %d\n" , b ) } func swap (x, y int ) int { var temp int temp = x x = y y = temp return temp; }
引用传递(指针传递) 引用传递指针参数传递到函数内,以下是交换函数 swap() 使用了引用传递:
1 2 3 4 5 6 7 func swap (x *int , y *int ) { var temp int temp = *x *x = *y *y = temp }
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 package mainimport "fmt" func main () { var a int = 100 var b int = 200 fmt.Printf("交换前,a 的值 : %d\n" , a ) fmt.Printf("交换前,b 的值 : %d\n" , b ) swap(&a, &b) fmt.Printf("交换后,a 的值 : %d\n" , a ) fmt.Printf("交换后,b 的值 : %d\n" , b ) } func swap (x *int , y *int ) { var temp int temp = *x *x = *y *y = temp }
init 函数与导包 现在在一个 go 的工程目录下有下面的文件
1 2 3 4 5 6 7 8 9 tree /F /A E:. | main.go | +---lib1 | lib1.go | |---lib2 lib2.go
main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( "fmt" "/go/lib1" "/go/lib2" ) func main () { fmt.Println("main()...." ) lib1.Lib1Test() lib2.Lib2Test() }
lib1.go
1 2 3 4 5 6 7 8 9 10 11 12 package lib1import "fmt" func Lib1Test () { fmt.Println("Lib1Test()...." ) } func init () { fmt.Println("Lib1 init()...." ) }
lib2.go
1 2 3 4 5 6 7 8 9 10 11 12 13 package lib2import "fmt" func Lib2Test () { fmt.Println("Lib2Test()...." ) } func init () { fmt.Println("Lib2 init()...." ) }
import 导包的时候
import _ “fmt”: 给 fmt 包起⼀个别名,匿名, ⽆法使⽤当前包的⽅法,但是 会执⾏当前的包内部的 init()⽅法 import aa “fmt” 给 fmt 包起⼀个别名,aa, aa.Println()来直接调⽤ import . “fmt” 将当前 fmt 包中的全部⽅法,导⼊到当前本包的作⽤中,fmt 包中 的全部的⽅法可以直接使⽤ API 来调⽤,不需要 fmt.API 来调⽤ defer defer 的执行顺序
执行顺序是 fun3() –> fun2() –> fun1()
defter 和 return 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 package mainfunc deferFunc () int { println ("defer func called... " ) return 0 } func returnFunc () int { println ("return func called... " ) return 0 } func returnAndDefer () int { defer deferFunc() return returnFunc() } func main () { returnAndDefer() }
return 之后的语句先执⾏,defer 后的语句后执⾏
切片和 slice 数组 数组长度是固定的 固定长度的数据在传参的时候是严格匹配数组类型的
1 2 3 4 5 6 7 8 9 10 package mainfunc main () { var myArray1 = [10 ]int myArray2 := [10 ]int {1 ,2 ,3 ,4 } myArray3 := [4 ]int {11 ,22 ,33 ,44 } } func printArray (myArray [4]int ) { }
动态数组 slice 动态数组的参数上是引用传递 而且不同元素长度的动态数组他们的形参是一致
声明方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport ( "fmt" ) func main () { myArray := []int {1 , 2 , 3 , 4 } slice := []int {1 , 2 , 3 } var slice1 []int slice1 = make ([]int , 3 ) var slice2 = make ([]int , 3 , 5 ) slice3 := make ([]int , 3 ) fmt.Println(slice, slice1, slice2, slice3) fmt.Println(myArray) } func printArray (myArray []int ) { for _, v := range myArray { fmt.Println(v) } }
使用方式 切片的容量和追加:切片的长度和容量不同,长度表示左指针至右指针的距离,容量表示左指针至底层数组末尾的距离
切⽚的扩容机制,append的时候,如果⻓度增加后超过容量,则将容量增加2倍
切片截取 可以通过设置下限及上限来设置截取切片*[lower-bound:upper-bound]*,实例如下
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 35 36 37 package mainimport "fmt" func main () { numbers := []int {0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 } printSlice(numbers) fmt.Println("numbers ==" , numbers) fmt.Println("numbers[1:4] ==" , numbers[1 :4 ]) fmt.Println("numbers[:3] ==" , numbers[:3 ]) fmt.Println("numbers[4:] ==" , numbers[4 :]) numbers1 := make ([]int ,0 ,5 ) printSlice(numbers1) number2 := numbers[:2 ] printSlice(number2) number3 := numbers[2 :5 ] printSlice(number3) } func printSlice (x []int ) { fmt.Printf("len=%d cap=%d slice=%v\n" ,len (x),cap (x),x) }
执行以上代码输出结果为:
1 2 3 4 5 6 7 8 len=9 cap =9 slice=[0 1 2 3 4 5 6 7 8] numbers == [0 1 2 3 4 5 6 7 8] numbers[1:4] == [1 2 3] numbers[:3] == [0 1 2] numbers[4:] == [4 5 6 7 8] len=0 cap =5 slice=[] len=2 cap =9 slice=[0 1] len=3 cap =7 slice=[2 3 4]
append() 和 copy() 函数 如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。下面的代码描述了从拷贝切片的 copy 方法和向切片追加新元素的 append 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport "fmt" func main () { var numbers []int printSlice(numbers) numbers = append (numbers, 0 ) printSlice(numbers) numbers = append (numbers, 1 ) printSlice(numbers) numbers = append (numbers, 2 ,3 ,4 ) printSlice(numbers) numbers1 := make ([]int , len (numbers), (cap (numbers))*2 ) copy (numbers1,numbers) printSlice(numbers1) } func printSlice (x []int ) { fmt.Printf("len=%d cap=%d slice=%v\n" ,len (x),cap (x),x) }
输出结果
1 2 3 4 5 len=0 cap =0 slice=[] len=1 cap =1 slice=[0] len=2 cap =2 slice=[0 1] len=5 cap =6 slice=[0 1 2 3 4] len=5 cap =12 slice=[0 1 2 3 4]
map 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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 package mainimport ( "fmt" ) func main () { var test1 map [string ]string test1 = make (map [string ]string , 10 ) test1["one" ] = "php" test1["two" ] = "golang" test1["three" ] = "java" fmt.Println(test1) test2 := make (map [string ]string ) test2["one" ] = "php" test2["two" ] = "golang" test2["three" ] = "java" fmt.Println(test2) test3 := map [string ]string { "one" : "php" , "two" : "golang" , "three" : "java" , } fmt.Println(test3) language := make (map [string ]map [string ]string ) language["php" ] = make (map [string ]string , 2 ) language["php" ]["id" ] = "1" language["php" ]["desc" ] = "php是世界上最美的语言" language["golang" ] = make (map [string ]string , 2 ) language["golang" ]["id" ] = "2" language["golang" ]["desc" ] = "golang抗并发非常good" fmt.Println(language) fmt.Println(language) }
面向对象特征 封装 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 Human struct { name string sex string } func (this *Human) Eat() { fmt.Println(this.name, "Humen.Eating()..." ) } func (this *Human) Walk() { fmt.Println(this.name, "Humen.Walking()..." ) } func (this *Human) setName(name string ) { this.name = name } func main () { humanTest := Human{name: "月晕" , sex: "男" } humanTest.Eat() humanTest.Walk() humanTest.setName("月晕2" ) humanTest.Eat() humanTest.Walk() }
(this Human)
代表的只是值拷贝 而(this *Human)
是传递的*Human
类型 go 会取地址传进去 即可以修改到原属性的值
类名、属性名、方法名 首字母大写表示对外(其他包)可以访问,否则只能够在本包内访问
继承 父类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type Human struct { name string sex string } func (this *Human) Eat() { fmt.Println(this.name, "Humen.Eating()..." ) } func (this *Human) Walk() { fmt.Println(this.name, "Humen.Walking()..." ) } func (this *Human) setName(name string ) { this.name = name }
子类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type SuperMan struct { Human level int } func (this *SuperMan) Eat() { fmt.Println("SuperMan.Eat()... " ) } func (this *SuperMan) Print() { fmt.Println("name = " , this.name) }
定义子类
1 2 3 4 5 var s SuperMans.name = "yueyun" s.sex = "♂" s.level = 98
多态 基本要素
有一个父类(有接口)
1 2 3 4 5 6 type AnimalIF interface { Sleep() GetColor() string GetType() string }
有子类(实现了父类的全部接口调用方法)
1 2 3 4 5 6 7 8 9 10 11 12 13 typo Cat struct { color String } func (this *Cat) Sleep() { fmt.Println("Cat is Sleep" ) } func (this *Cat) GetColor() string { return this.color } func (this *Cat) GetType() string { return "Cat" }
父类型的变量(指针)指向(引用)子类的具体数据变量
1 2 3 4 5 6 7 8 var animal AnimalIF animal = &Cat{"Green" } animal.Sleep() func showAnimal (animal AnimalIF) { animal.Sleep() fmt.Println("color =" , animal.GetColor) }
interface 通用万能类型 interface{}
– 空接口
int string float
等 都是先了interface{}
就可以用interface
类型引用任意的数据类型
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 myFunc (arg interface {}) { fmt.Println("myFunc is called..." ) fmt.Println(arg) value, ok := arg.(string ) if !ok { fmt.Println("arg is not string type" ) } else { } } func main () { var i int = 5 var s string = "Hello world" myFunc(i) myFunc(s) }
类型断言
1 2 3 4 5 6 7 8 9 func myFunc (arg interface {}) { value, ok := arg.(string ) if !ok { fmt.Println("arg is not string type" ) } else { } }
反射 pair
pair是一个对 不管怎么传递都不会变化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func main () { var a string a = "yueyun" var allType interface {} allType = a str, _ := allType.(string ) fmt.Println(str) }
pair在传递的过程中保持不变
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" "io" "os" ) func main () { tty, err := os.OpenFile("/dev/tty" , os.O_RDWR, 0 ) if err != nil { fmt.Println("open file error: " , err) return } var r io.Reader r = tty var w io.Writer w = r.(io.Writer) w.Write([]byte ("hello world\n" )) }
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 35 36 37 38 package mainimport "fmt" type Writer interface { WriteBook() } type Reader interface { ReadBook() } type Book struct {} func (this *Book) WriteBook() { fmt.Println("write book" ) } func (this *Book) ReadBook() { fmt.Println("read book" ) } func main () { b := &Book{} var r Reader r = b r.ReadBook() var w Writer w = r.(Writer) w.WriteBook() }
pair 是一直存在而且不会变化
reflect包 包括基本函数 Valueof || Typeof
1 2 3 4 func Valueof (i interface {}) Value {...}func Typeof (i interface {}) Type {...}
reflect基本用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport ( "fmt" "reflect" ) func reflectNum (arg interface {}) { fmt.Println("type: " , reflect.TypeOf(arg)) fmt.Println("value: " , reflect.ValueOf(arg)) } func main () { var num float64 = 1.114514 reflectNum(num) }
复杂类型
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 35 36 37 38 39 40 41 42 43 44 45 package mainimport ( "fmt" "reflect" ) type User struct { Id int Name string Age int } func (this User) Call() { fmt.Println("User is called..." ) fmt.Printf("%v\n" , this) } func main () { user := User{1 , "yueyun" , 18 } DoFiledAndMethod(user) } func DoFiledAndMethod (input interface {}) { inputType := reflect.TypeOf(input) fmt.Println("inputType is :" , inputType.Name()) inputValue := reflect.ValueOf(input) fmt.Println("inputValue is :" , inputValue) for i := 0 ; i < inputType.NumField(); i++ { field := inputType.Field(i) value := inputValue.Field(i).Interface() fmt.Printf("%s: %v = %v\n" , field.Name, field.Type, value) } for i := 0 ; i < inputType.NumMethod(); i++ { m := inputType.Method(i) fmt.Printf("%s: %v\n" , m.Name, m.Type) } }
结构体标签 json 解编码 orm映射关系
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 package mainimport ( "fmt" "reflect" ) type resume struct { Name string `info:"name" docs:"我的名字"` Sex string `info:"sex"` } func findTag (str interface {}) { t := reflect.TypeOf(str).Elem() for i := 0 ; i < t.NumField(); i++ { taginfo := t.Field(i).Tag.Get("info" ) tagdoc := t.Field(i).Tag.Get("docs" ) fmt.Println("info:" , taginfo, "docs:" , tagdoc) } } func main () { var re resume findTag(&re) }
json转换 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 package mainimport ( "encoding/json" "fmt" ) type Moive struct { Title string `json:"title"` Year int `json:"year"` Price int `json:"price"` Actors []string `json:"actors"` } func main () { movie := Moive{"喜剧之王" , 2000 , 10 , []string {"周星驰" , "张柏芝" }} jsonStr, err := json.Marshal(movie) if err != nil { fmt.Println("json marshal error:" , err) return } fmt.Printf("%s\n" , jsonStr) myMoive := Moive{} err = json.Unmarshal(jsonStr, &myMoive) if err != nil { fmt.Println("json unmarshal error:" , err) return } fmt.Printf("%v\n" , myMoive) }
高级语法 goroutine基本模型和调度设计策略 早期的单线程操作系统
存在的问题
单一执行流程, 计算机只能一个一个任务的去处理 进程阻塞所带来的CPU浪费时间(密集IO
处理 ) 多线程/多进程操作系统
多进程/多线程 解决了阻塞的问题
但是当cpu工作在切换线程的时候会存在很高的切换成本 导致CPU利用率很低, 进程/线程数量越大 切换的成本就越大 也就越浪费 多线程 随着同步竞争 (如 锁 竞争资源冲突等) 开发设计会变得越来越复杂 且内存占用多
协程
co-routine 的设计
Goland 对协程的处理: 内存很小可以大量 灵活调度 可以经常切换
Goland对早期调度器的处理
创建 销毁 调度G都需要每个M获取锁 形成了激烈的锁竞争 M转移G的时候会造成延迟和额外的系统负载 系统调用(CPU与M之间的切换)导致频繁的线程阻塞和取消阻塞增加了系统开销
GMP
go现在的设计
调度器的设计策略 复用线程 wrok stealing 机制 hand off 机制 利用并行 GOMAXPROCS
限定P的个数 = cpu核数 / 2
抢占 平均特点 抢占式
全局G队列 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 25 26 27 28 package mainimport ( "fmt" "time" ) func newTask () { i := 0 for { i++ fmt.Println("new Goroutine: i = " , i) time.Sleep(1 * time.Second) } } func main () { go newTask() i := 0 for { i++ fmt.Println("main Goroutine: i = " , i) time.Sleep(1 * time.Second) } }
goroutine匿名函数写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func main () { go func () { defer fmt.Println("A.defer" ) func () { defer fmt.Println("B.defer" ) runtime.Goexit() fmt.Println("B" ) }() fmt.Println("A" ) }() for { time.Sleep(time.Second * 1 ) } }
1 2 3 4 5 6 7 8 9 10 func main () { go func (a int , b int ) bool { defer fmt.Println("A.defer" ) fmt.Println("a=" , a, "b=" , b) return false }(10 , 20 ) for { time.Sleep(time.Second * 1 ) } }
runtime.Goexit()
退出当前的goroutine
channel 管道 基本用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func main () { ch := make (chan string ) go func () { defer println ("func goroutine exit." ) println ("func goroutine starts sending..." ) ch <- "EXPLOSION!!!" }() println ("main goroutine starts receiving..." ) str := <-ch println ("main goroutine received:" , str) defer println ("main goroutine exit." ) }
基本分析图示
chan 管道具有连接两个协程并且具有同步阻塞的功能
具有缓冲的channel 无缓冲的channel
两个goroutine都达到通道 左侧goroutine开始发送数据到通道中 这时右侧的goroutine会在通道中被锁住 直到交换完成 右侧的goroutine开始接受数据 左侧的goroutine也将会被锁住 交换数据完成 这时两个goroutine会被解锁 可以继续跑下面的流程 有缓冲的channel
左侧的goroutine 传输数据 直到管道写满或者自己停止 右侧的goroutine获取数据 左侧的goroutine新发送数据 左侧发送新增 右边接受 这两种操作既不是同步的也不会相互阻塞 所有的发送和接受完毕 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func main () { c := make (chan int , 3 ) fmt.Println("len(c)=" , len (c), "cap(c)=" , cap (c)) go func () { defer fmt.Println("子go程结束" ) for i := 0 ; i < 5 ; i++ { c <- i fmt.Println("子go程正在运行,发送的元素=" , i, "len(c)=" , len (c), "cap(c)=" , cap (c)) } }() time.Sleep(2 * time.Second) for i := 0 ; i < 5 ; i++ { num := <-c fmt.Println("num=" , num) } fmt.Println("main go程结束" ) }
当channel已经满 在向里面写数据就会阻塞 当channel为空 从里面取数据也会阻塞 channel的关闭 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func main () { c := make (chan int ) go func () { for i := 0 ; i < 5 ; i++ { c <- i } close (c) }() for { if data, ok := <-c; ok { fmt.Println(data) } else { break } } fmt.Println("main go程结束" ) }
channel 不像文件一样要经常的打开和关闭 只有当确定了没有任何发送的数据了或者想结束range循环才会选择去关闭channel
关闭channel后 无法在向channel 再发送数据(引发panic) 关闭channel后 可以继续从channel接受数据 关于nil channel 无论收发都会去阻塞 channel和range 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { c := make (chan int ) go func () { for i := 0 ; i < 5 ; i++ { c <- i } close (c) }() for data := range c { fmt.Println(data) } fmt.Println("main go程结束" ) }
channel和select 单个流程下一个go只能监听一个channel的状态 select可以完成监控多个channel的状态
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 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) } 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 } } }
goland context 实现原理 context是goland中的经典工具,主要在异步场景中用于实现并发协调以及对 goroutine 的生命周期控制. 除此之外,context 还兼有一定的数据存储能力.
核心数据结构 context.Context Go modules go modules 模式 使用go mod
和go modules
进行项目依赖管理
Go PATH的工作模式弊端: 无版本控制的概念, 无法同步一致第三方版本号 无法指定当前
go mod 的一些命令
go mod init
生成go.mod 文件go mod download
下载go.mod
文件中指明的所有依赖go mod tidy
整理现有的依赖go mod vendor
导出项目所有的依赖到vendor目录… 等等等 go mod 的环境变量
GO111MODULE: auto||on||off 设置 go env -w GO111MODULE=on
GOPROXY: 默认值 国内代理: “https://mirrors.aliyun.com/goproxy/ “ $ go env -w GOPROXY=https://goproxy.cn,direct
direct 用于指示Go回源到模块版本的源地址去抓取 GOSUMDB: 用于在拉取模块版本时(无论是从源站拉取还是通过 Go module proxy 拉取)保证拉取到的模块版本数据未经过篡改,若发现不一致,也就是可能存在篡改,将会立即中止 GONOPROXY/GONOSUMDB/GOPRIVATE: 建议直接设置 GOPRIVATE,它的值将作为 GONOPROXY 和 GONOSUMDB 的默认值,所以建议的最佳姿势是直接使用 GOPRIVATE 。$ go env -w GOPRIVATE="git.example.com,github.com/eddycjy/mquote"
不经过 GOPROXY, GOSUMDB go env 来查看环境变量
go modules 初始化项目 开启Go Modules模块 保证GOMODULE=on
初始化项目 任意文件夹下面创建一个项目
1 $ mkdir -p ~/project/module_test
创建go mod
文件同时起当前项目名
1 $ go init github.com/yueyun/module_test
生成go.mod
文件 内容为
1 2 module github.com/yueyun/module_test go 1.21.6
在该项目编写代码, 如果源代码依赖某个库 如(github.com/apprehen/zinx/znet)可以 手动down: go get github.com/apprehen/zinx/znet 自动donw
go mod 文件下面会新增加一行代码
1 require github.com/apprehen/zinx v0.0.0-20200315083925-f09df55dc746,indirect
含义当前模块依赖github.com/apprehen/zinx 依赖版本是后面的
//indirct 表示间接依赖 因为项目直接依赖的是znet包 间接依赖的是zinx包
生成一个go.sum 文件 go.sum文件的作用 罗列当前项目直接引用或间接的依赖所有模块版本 保证今后项目依赖的版本不会被篡改h1:hash
: 表示整体项目的zip文件打开之后的全部文件的校验和来生成的hash 如果不存在 则表示依赖的库可能用不上 xxx/go.mod h1:hash go.mod文件做的hash
Go语言项目案例 IM-System 构建基础的server main.go 1 2 3 4 5 6 package mainfunc main () { server := NewServer("127.0.0.1" , 8800 ) server.Start() }
server.go server类型 1 2 3 4 type Server struct { IP string Port int }
server方法 创建一个server对象
1 2 3 4 5 6 7 func NewServer (IP string ,Port int ) *Server { server := &Server{ IP: IP, Port: port } return server }
启动 server服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func (this *Server) Start () { listener,err := net.listen("tcp" ,fnt.Sprintf("%s:%d" ,this.IP,this.Port)) if err = !nil { fmt.PrintLn("net.Listen err:" ,err) return } defer listener.Close() for { conn,err = listener.Accept() if err!= nil { fmt.Println("listener.Accept err:" ,err) continue } go this.Handler(conn) } }
处理连接业务
1 2 3 func (this *Server) Handler(conn net.Conn) { fmt.Println("建立连接成功" ) }
用户上线功能 user.go user类型 1 2 3 4 5 6 type User struct { Name string Addr string C chan string conn net.Conn }
方法 创建一个user对象
1 2 3 4 5 6 7 8 9 10 11 12 func NewUser (conn net.Conn) *User { userAddr := conn.RemoteAddr().String user := &User { Name: userAddr Addr: userAddr C: make (chan string ) conn: conn } go user.ListenMessage() return user }
监听消息user对应的channel消息
1 2 3 4 5 6 func (this *User) ListenMessage() { for { msg := <- this.C this.conn.Write([]byte (msg + "\n" )) } }
server.go server类型 新增OnlineMap和Message属性
1 2 3 4 5 6 7 8 9 type Server struct { IP string Port int OnlineMap map [string ]*User mapLock sync.RWMutex Message chan string }
在处理客户端上线的Handler创建并添加用户
1 2 3 4 5 6 7 8 9 10 11 12 func (this *Server) Handler(conn net.Conn) { fmt.Println("建立连接成功" ) user := NewUser(conn) this.mapLock.Lock() this.OnlineMap[user.Name] = user this.BroadCast(user, "已上线" ) this.mapLock.Unlock() select {} }
新增广播消息方法
1 2 3 4 func (this *Server) BroadCast(user *User, msg string ) { sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg this.Message <- sendMsg }
新增监听广播消息channel方法
1 2 3 4 5 6 7 8 9 10 11 func (this *Server) ListenMessager() { for { msg := <-this.Message this.mapLock.Lock() for _, cli := range this.OnlineMap { cli.C <- msg } this.mapLock.Unlock() } }
用一个goroutine单独监听Message
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func (this *Server) Start() { listener, err := net.Listen("tcp" , fmt.Sprintf("%s:%d" , this.IP, this.Port)) if err != nil { fmt.Println("net.Listen err:" , err) return } defer listener.Close() go this.ListenMessager() for { conn, err := listener.Accept() if err != nil { fmt.Println("listener.Accept err:" , err) continue } go this.Handler(conn) } }
用户消息广播机制 server.go 完善handle处理业务的方法 启动一个针对当前客户端的读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 25 26 27 28 29 30 func (this *Server) Handler(conn net.Conn) { user := NewUser(conn) this.mapLock.Lock() this.OnlineMap[user.Name] = user this.BroadCast(user, "已上线" ) this.mapLock.Unlock() go func () { buf := make ([]byte , 4096 ) for { n, err := conn.Read(buf) if n == 0 { this.BroadCast(user, "下线" ) return } if err != nil && err != io.EOF { fmt.Println("conn.Read err:" , err) return } msg := string (buf[:n-1 ]) this.BroadCast(user, msg) } }() select {} }
用户业务层封装 user.go user类型新增server关联
1 2 3 4 5 6 7 type User struct { Name string Addr string C chan string conn net.Conn server *Server }
新增Online方法
1 2 3 4 5 6 7 func (this *User) Online() { this.server.mapLock.Lock() this.server.OnlineMap[this.Name] = this this.server.BroadCast(this, "已上线" ) this.server.mapLock.Unlock() }
新增Offline方法
1 2 3 4 5 6 7 func (this *User) Offline() { this.server.mapLock.Lock() delete (this.server.OnlineMap, this.Name) this.server.mapLock.Unlock() this.server.BroadCast(this, "下线" ) }
新增DoMessage方法
1 2 3 func (this *User) DoMessage(msg string ) { this.server.BroadCast(this, msg) }
server.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 func (this *Server) Handler(conn net.Conn) { user := NewUser(conn, this) user.Online() go func () { buf := make ([]byte , 4096 ) for { n, err := conn.Read(buf) if n == 0 { user.Offline() return } if err != nil && err != io.EOF { fmt.Println("conn.Read err:" , err) return } msg := string (buf[:n-1 ]) user.DoMessage(msg) } }() select {} }
在线用户查询 user.go 提供SendMsg向对象客户端发送消息API
1 2 3 func (this *User) SendMsg(msg string ) { this.conn.Write([]byte (msg)) }
在DoMessage()
方法中 加上对who
指令的处理 返回在线用户的信息
1 2 3 4 5 6 7 8 9 10 11 12 13 func (this *User) DoMessage(msg string ) { if msg == "who" { this.server.mapLock.Lock() for _, user := range this.server.OnlineMap { onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "在线...\n" this.SendMsg(onlineMsg) } } else { this.server.BroadCast(this, msg) } }
修改用户名 消息格式为rename
然后修改服务器内部状态改成AwaitUserName
在对消息处理设置成新的用户名
修改 DoMessage方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func (this *User) DoMessage(msg string ) { switch this.state { case NormalState: if msg == "who" { this.server.mapLock.Lock() for _, user := range this.server.OnlineMap { onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "在线...\n" this.SendMsg(onlineMsg) } this.server.mapLock.unlock() } else if msg == "rename" { this.state = AwaitUserNameState this.SendMsg("请输入新的用户名: " ) } else { this.server.BroadCast(this, msg) } case AwaitUserNameState: this.Rename(msg) this.state = NormalState } }
新增加 ReName
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func (this *User) Rename(newName string ) { if newName == "" { this.SendMsg("用户名不能为空\n" ) return } _, ok := this.server.OnlineMap[newName] if ok { this.SendMsg("用户名已经被使用\n" ) return } else { this.SendMsg("用户名正在更改...\n" ) this.server.mapLock.Lock() delete (this.server.OnlineMap, this.Name) this.server.OnlineMap[newName] = this this.server.mapLock.Unlock() this.Name = newName this.SendMsg("您已经更新用户名:" + this.Name + "\n" ) } }
超时强踢功能 在用户的hander()中 goroutine中 添加用户活跃的channel 一旦有消息 就向该channel发送数据
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 func (this *Server) Handler(conn net.Conn) { user := NewUser(conn, this) user.Online() isLive := make (chan bool ) go func () { buf := make ([]byte , 4096 ) for { n, err := conn.Read(buf) if n == 0 { user.Offline() return } if err != nil && err != io.EOF { fmt.Println("conn Read err:" , err) return } msg := string (buf[:n-1 ]) user.DoMessage(msg) isLive <- true } }()
在用户的hander()中 添加定时器功能 超时则强踢
1 2 3 4 5 6 7 8 9 10 11 12 13 select { case <-isLive: case <-time.After(time.Second * 10 ): user.SendMsg("你被踢了\n" ) close (user.C) conn.Close() return }
实现私聊功能 消息格式是 to|yueyun|你好啊我是...
user.go 在DoMessage()方法中 加上对to|yueyun|你好啊
指令的处理 返回在线用户的信息
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 35 36 37 if msg == "who" { this.server.mapLock.Lock() for _, user := range this.server.OnlineMap { onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "在线...\n" this.SendMsg(onlineMsg) } this.server.mapLock.Unlock() } else if msg == "rename" { this.state = AwaitUserNameState this.SendMsg("请输入新的用户名: " ) } else if len (msg) > 4 && msg[:3 ] == "to|" { remoteName := strings.Split(msg, "|" )[1 ] if remoteName == "" { this.SendMsg("消息格式不正确,请使用\"to|yueyun|消息内容\"格式\n" ) return } remoteUser, ok := this.server.OnlineMap[remoteName] if !ok { this.SendMsg("该用户名不存在\n" ) return } content := strings.Split(msg, "|" )[2 ] if content == "" { this.SendMsg("无消息内容,请重发\n" ) return } remoteUser.SendMsg(this.Name + "对你说:" + content + "\n" ) } else { this.server.BroadCast(this, msg) }