本文最后更新于:2 年前
Go非常有意思的一门语言,这一次的学习偏向于基础知识,后面可能还会接触工程项目,总之先把基础打好!!!
基础环境配置 具体步骤:
在Window上直接下载Golang加上Goland就好,这里比较简单我不做过多的记录,电脑崩了好几次,这已经是第二次配置Go语言了orz。
下面记录一下在Linux中配置环境的过程:
Go的序言 优势: 部署简单:
可以直接编译成机器码(go build)
不依赖其他库(编译生成的文件就全部整好了!!!)
直接运行就可以部署!!!(./生成的可执行文件就可以完成服务器的运行嗷!!!)
静态类型语言:
编译的时候就可以查出来隐藏的大多数问题,防止跑的过程中出现问题。
语言层面的并发:
强大的标准化:
runtime系统调度机制:
帮助垃圾回收
帮助我们做一些Go的调度的平均分配
很多的优化机制
高效的GC垃圾回收:
1.8版本之后就加入了三色标记和混合写屏障,效率非常搞了
丰富的标准库:
简单易学:
25个关键字
C基因,内嵌C都支持???
面向对象的特征(继承,多态,封装)
跨平台
大厂领军:
适用领域:
云计算基础设施:Docker , k8s , etcd , consul , cloudflare CDN等等
基础后端软件:tidb , influxdb
微服务:go-kit , micro , monzo bank的bilibili等
互联网基础设施:以太坊 , hyperledger等
不足:
包管理的不足
没有泛化,GO计划2.0上加上(传言)
所有的Exception都用Error来处理
对于C的降级处理,并非无缝衔接
认识Go程序 语法: 简单程序入门: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package main import ( "fmt" "time" )func main () { fmt.Println("hello Go!!!" ) time.Sleep(1 * time.Second) }
一个Package中只有一个Main入口哦!!!
函数的{
一定是和函数名在同一行的,否则编译错误。
变量声明: 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" "math" )func main () { var a int fmt.Println("a = " ,a) fmt.Printf("type of a = %T\n" ,a) var b int = 100 fmt.Println("b = " ,b) fmt.Printf("type of b = %T\n" ,b) var c = 200 fmt.Println("c = " ,c) fmt.Printf("type of c = %T\n" ,c) var d = "abcd" fmt.Printf("d = %s, type of d = %T\n" ,d,d) e := math.Pi fmt.Printf("e = %f, type of e = %T\n" ,e,e) }
上面就是声明变量的方法。
如果想要全局变量,把变量的声明放在方法外面就成嗷!!!
1 2 3 var gA int = 100 var gB = 200
上面的方法一二三都可以声明全局变量,但是方法四这种简单的是不适用的嗷!!!
var xx , yy int = 100, 200
var kk , ll = 100 , "Alexander"
多行的多变量声明:
1 2 3 4 var ( vv int = 100 jj bool = true )
语法四声明变量:
1 2 3 xx , yy := 100 ,200 fmt.Println(xx) fmt.Println(yy)
Const与Iota知识点注意事项 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 package mainimport "fmt" const ( BEIJING = 10 *iota SHANGHAI SHENZHEN )const ( a, b = iota +1 , iota +2 c, d e, f g, h = iota *2 , iota *3 i,k )func main () { const length int = 10 fmt.Println("length = " ,length) fmt.Println("BEIJING = " ,BEIJING) fmt.Println("SHANGHAI = " ,SHANGHAI) fmt.Println("SHENZHEN = " ,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 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 53 54 55 56 57 58 59 60 61 62 63 64 65 package mainimport "fmt" func foo1 (a string , b int ) int { fmt.Println("--- foo1 ---" ) fmt.Println("a = " ,a) fmt.Println("b = " ,b) c := 100 return c }func foo2 (a string , b string ) (int ,int ) { fmt.Println("--- foo2 ---" ) fmt.Println("a = " ,a) fmt.Println("b = " ,b) return 666 ,777 }func foo3 (a string , b string ) (r1 int ,r2 int ) { fmt.Println("--- foo3 ---" ) fmt.Println("a = " ,a) fmt.Println("b = " ,b) r1 = 1000 r2 = 2000 return }func foo4 (a string , b string ) (r1 ,r2 int ) { fmt.Println("--- foo4 ---" ) fmt.Println("a = " ,a) fmt.Println("b = " ,b) r1 = 1000 r2 = 2000 return }func main () { c := foo1("foo1" ,6666 ) fmt.Println("c = " ,c) ret1 , ret2 := foo2("foo2" ,"Hah, this is another one!!!" ) fmt.Println(ret1) fmt.Println(ret2) ret1 , ret2 = foo3("foo3" ,"Hah, this is the third one!!!" ) fmt.Println("ret1 = " ,ret1," ret2 = " ,ret2) ret1 , ret2 = foo4("foo4" ,"Hah, this is the fourth one!!!" ) fmt.Println("ret1 = " ,ret1," ret2 = " ,ret2) }
summary:
匿名
匿名多返回值
有名称的返回值
Import导包路径和Init方法
有点像Java的初始化函数,在调用某个包中的方法的时候,先会执行Init()方法进行初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( "go2/GolangStudy/5-init/lib1" "go2/GolangStudy/5-init/lib2" )func main () { lib1.Lib1Test() lib2.Lib2Test() }
文件目录结构:
GoLand非常强,不用自己导入包,直接输入lib1就有提示的嗷!!!
1 2 3 4 5 6 7 8 9 10 11 package lib1import "fmt" func Lib1Test () { fmt.Println("Lib1Test()..." ) }func init () { fmt.Println("lib1 , init() ..." ) }
Import匿名及别名导包方式
导入的包如果不使用任何函数,那么会报错,如果我仅仅想要执行某个包中的初始化方法但是又用不到这个包中的任何方法呢???
匿名导入包:
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( _ "go2/GolangStudy/5-init/lib1" "go2/GolangStudy/5-init/lib2" )func main () { lib2.Lib2Test() }
如上就可以使得lib1中的Init()方法执行嗷!!!
如果匿名导入包的话是无法使用这个包的方法的,但是可以使得对应包的初始化方法执行嗷!!!
同理,可以给导入的包起别名嗷!!!
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( _ "go2/GolangStudy/5-init/lib1" mylib2 "go2/GolangStudy/5-init/lib2" )func main () { mylib2.Lib2Test() }
全部方法的导入方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( _ "go2/GolangStudy/5-init/lib1" . "go2/GolangStudy/5-init/lib2" )func main () { Lib2Test() }
类似于Python中的from package import *
点不要轻易使用,如果两个包中有同名的方法就会发生冲突嗷!!!
Golang中的指针速通
和C语言中的是一样的嗷!!!简单的参数就是传值!!!如果*a
和C语言是一样的嗷!!!就是传址操作嗷!!!
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" func swap (a, b int ) { var temp = a a = b b = temp }func swap2 (a, b *int ) { var temp = *a *a = *b *b = temp }func main () { a, b := 10 , 20 swap(a,b) fmt.Println("a = " ,a) fmt.Println("b = " ,b) a,b = 10 ,20 swap2(&a,&b) fmt.Println("a = " ,a) fmt.Println("b = " ,b) }
defer语句
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport "fmt" func main () { defer fmt.Println("main end1" ) defer fmt.Println("main end2" ) fmt.Println("main::hello go 1" ) fmt.Println("main::hello go 2" ) }
defer后面的语句在所有流程结束,代码结束之前执行,类似于Java中的finally,经常用于释放资源。
defer可以有多个,本质是进行了压栈,所以上面的end1在栈底,end2在栈首,执行的时候先main end2再main end1嗷!!!
另外一个Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport "fmt" func test () { fmt.Println("hehe, I am here !!!" ) }func main () { defer fmt.Println("main end" ) defer test() fmt.Println("main::hello go 1" ) fmt.Println("main::hello go 2" ) }
输出结果:
1 2 3 4 main::hello go 1 main::hello go 2 hehe, I am here !!! main end
defer要放在return之前哈,不然defer根本执行不到哇!!!return应该放在最后嗷!!!现在探究的是return和defer的执行顺序:
很好理解,当程序运行到}
的时候,才执行deter函数嗷,这个时候return早就执行完了嗷!!!deter是在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 mainimport "fmt" func test () { fmt.Println("hehe, I am here !!!" ) }func test2 () string { defer test() return "Hello this is the return function" }func main () { defer fmt.Println("main end" ) defer test() fmt.Println("main::hello go 1" ) fmt.Println("main::hello go 2" ) test2() fmt.Println("======================" ) }
1 2 3 4 5 6 main::hello go 1 main::hello go 2 hehe, I am here !!! ====================== hehe, I am here !!! main end
可以看到,deter是在 本方法执行结束之后执行的嗷!!!
Go中的Slice
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 printArray (myArray []int ) { for _,val := range myArray { fmt.Println(val) } myArray[0 ] = 100 }func main () { var myArray1 [10 ]int myArray2 := [10 ]int {1 ,2 ,3 ,4 } for i := 0 ; i < len (myArray1); i++ { fmt.Println(myArray1[i]) } fmt.Println("================" ) for index, value := range myArray2 { fmt.Println("index = " ,index,",value = " ,value) } fmt.Println("================" ) fmt.Printf("myArray1 type = %T\n" , myArray1) fmt.Printf("myArray2 type = %T\n" , myArray2) fmt.Println("================" ) myArray := []int {1 ,2 ,3 ,4 } fmt.Printf("myArray type = %T\n" , myArray) printArray(myArray) fmt.Println("====" ) for _, value := range myArray { fmt.Println(value) } fmt.Println("================" ) }
注意上面哦!!!如果我们写死了传递的数组如:myArray [4]int
这样的话,本质上也是值拷贝,不好修改元素,也不方便传值嗷!!!由此,动态数组非常重要嗷!!!
动态数组在传参上使用的类似于Java的引用传递,而且不同元素长度的动态数组,他们的形参是一致的嗷!!!
Slice的声明方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func main () { slice1 := []int {1 ,2 ,3 } fmt.Printf("len = %d, slice = %v\n" ,len (slice1),slice1) var slice2 []int slice2 = make ([]int , 3 ) fmt.Printf("len = %d, slice = %v\n" ,len (slice2),slice2) var slice3 []int = make ([]int ,3 ) fmt.Printf("len = %d, slice = %v\n" ,len (slice3),slice3) slice4 := make ([]int ,3 ) fmt.Printf("len = %d, slice = %v\n" ,len (slice4),slice4) }
1 2 3 if slice == nil { slice = make ([]int ,3 ) }
Slice的使用方式
1 2 var numbers = make ([]int , 3 , 5 ) fmt.Printf("len = %d , cap = %d , slice = %v\n" , len (numbers) , cap (numbers) , numbers)
ptr指向合法空间末尾,后面两个格子是非法空间,虽然无法访问,但是为位置已经开辟好了嗷!!!
1 2 numbers = append (numbers, 1 ) numbers = append (numbers, 2 )
向numbers中追加两个元素,这个时候numbers已经满了。
go会自动帮助我们再去开辟一段空间,默认容量拓展为原来容量的两倍(切片扩容机制)
如果不指定cap,默认的cap和你分配的len是一样嗷!!!
切片的截取:
1 2 3 4 5 6 7 8 s := []int {1 ,2 ,3 } s1 := s[0 :2 ] s2 := s[:] s[startIndex:] s[:endIndex]
默认截取指向的底层切片是同一个!!
如果我想要完成copy呢?
s2 := make([]int , 3)
copy(s3, s)
这个就类似于Java中的浅拷贝和深拷贝,上面这个意思就是将s中的内容依次拷贝到s3中嗷!!!
Go中的Map 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 func main () { var myMap1 map [string ]string if myMap1 == nil { fmt.Println("myMap1 is an empty map!!!" ) } myMap1 = make (map [string ]string , 2 ) myMap1["one" ] = "java" myMap1["two" ] = "python" myMap1["three" ] = "c++" fmt.Printf("%v , %d\n" , myMap1,len (myMap1)) myMap2 := make (map [int ]string ) myMap2[1 ] = "java" myMap3 := map [string ]string { "one" : "php" , "two" : "python" , } fmt.Println(myMap3) }
和Java很像,声明了一个这种变量,一定要初始化(在Go中叫做分配空间),才能够使用嗷!!!
Map的使用方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 cityMap := make (map [string ]string ) cityMap["china" ] = "BeiJing" cityMap["Japan" ] = "Tokyo" cityMap["USA" ] = "NewYork" delete (cityMap , "Japan" ) cityMap["USA" ] = "DC" for key, value := range cityMap{ fmt.Println("key = " ,key) fmt.Println("value = " ,value) }
1 2 3 4 5 6 7 8 9 10 11 12 13 func printMap (cityMap map [string ]string ) { for key, value := range cityMap{ fmt.Println("key = " ,key) fmt.Println("value = " ,value) } }func changeValue (cityMap map [string ]string ) { cityMap["England" ] = "London" } printMap(cityMap)
这里要注意,调用这个函数的时候,本质上是一个引用传递,实质上传递的是cityMap的指针嗷!!!指向的是同一块空间嗷!!!
struct的基本定义和使用
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" type myint int type Book struct { title string auth string }func changeBook (book Book) { book.auth = "tuzijiejie" }func changeBook2 (book *Book) { book.auth = "tuzijiejie" }func main () { var a myint = 10 fmt.Println("a = " ,a) var book1 Book book1.title = "Golang" book1.auth = "tuzi" fmt.Printf("%v\n" ,book1) changeBook(book1) fmt.Printf("%v\n" ,book1) changeBook2(&book1) fmt.Printf("%v\n" ,book1) }
类的表示:
将结构体和方法分开,此外要注意大小写,大写才是能够被外部访问到的嗷!!!
下面这一个struct搭配上下面专属的方法,构成了一个完整的类嗷!!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type Hero struct { Name string Ad int Level int }func (this *Hero) GetName(){ fmt.Println("Name = " , this.Name) }func (this *Hero) SetName(newName string ){ this.name = newName }func (this *Hero) Show(){ fmt.Println("Name = " ,this.Name) fmt.Println("Ad = " ,this.Ad) fmt.Println("Level = " ,this.Level) }
this Hero指的是和Hero这个struct绑定,调用这个属性的是Hero对象,实际调用的对象可以用this来指代
1 2 3 4 5 hero := Hero{Name: "tuzi" , Ad: 100 , Level: 1 } hero.Show() hero.SetName("lisi" ) hero.Show()
虽然语法没有任何错误,但是所有的方法传入的对象(this Hero),都是该对象的一个副本(拷贝)
因此要改为 指针 !!!(this *Hero),这样写,当对象调用的时候,才默认是传递引用嗷!!!
例如Level -> level,这个只有类中可以访问嗷!!!如果方法是大写,其他的包中是能够访问的。但是如果是小写,只有本包之内能访问这个方法。
类的继承: 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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 package mainimport "fmt" type Human struct { name string sex string }func (this *Human) Eat(){ fmt.Println("Human eat ......" ) }func (this *Human) Walk() { fmt.Println("Human walk ......" ) }type SuperMan struct { Human level int }func (this *SuperMan) Eat() { fmt.Println("SuperHuman eat ......" ) }func (this *SuperMan) Fly() { fmt.Println("SuperHuman fly ......" ) }func (this *SuperMan) Print() { fmt.Println("========================" ) fmt.Println("name = " ,this.name) fmt.Println("gender = " ,this.sex) fmt.Println("level = " ,this.level) }func main () { h := Human{"zhang3" ,"male" } h.Eat() h.Walk() fmt.Println("======================" ) s := SuperMan{Human{"Clerk" ,"male" },100 } s.Eat() s.Walk() s.Fly() var s2 SuperMan s2.name = "Alexander Liu" s2.sex = "male" s2.level = 88 s2.Print() }
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package mainimport "fmt" type Animal interface { Sleep() GetColor() string GetType() string }type Cat struct { color string }func (this *Cat) Sleep() { fmt.Println("Cat is sleeping" ) }func (this *Cat) GetColor() string { return this.color }func (this *Cat) GetType() string { return "Cat" }type Dog struct { color string }func (this *Dog) Sleep() { fmt.Println("Dog is sleeping" ) }func (this *Dog) GetColor() string { return this.color }func (this *Dog) GetType() string { return "Dog" }func showAnimal (animal Animal) { animal.Sleep() fmt.Println("kind = " ,animal.GetType()) fmt.Println("color = " ,animal.GetColor()) }func main () { var animal Animal animal = &Cat{"White" } showAnimal(animal) fmt.Println("=============" ) animal = &Dog{"Black" } showAnimal(animal) }
多态的基本要素:
有一个父类(有接口)。
有子类(实现了父类的全部接口方法)。
父类的类型的变量(指针)指向(引用)子类的具体数据变量。
通用万能类型(空接口):
interface {}
所有的int , string , float64 , struct ……都实现了interface{} ——> 可以使用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 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 { fmt.Println("arg is string , value = " ,value) fmt.Printf("value type is %T\n" ,arg) } }func main () { myFunc(0 ) fmt.Println("=============" ) myFunc("0" ) }
底层变量的构造:
1 2 3 4 5 6 7 8 9 10 var a string a = "abc" var allType interface {} allType = a str , _ := allType.(string )
具体类型:
虽然拿着它的Interface一直在变,但是在记录的pair中的数据,始终如一嗷!!!
Reflect反射机制
常用方式:
下面这两个其实就是获得了传入的interface的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 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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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 reflectNum (arg interface {}) { fmt.Println("type : " ,reflect.TypeOf(arg)) fmt.Println("value : " ,reflect.ValueOf(arg)) }func DoFiledAndMethod (input interface {}) { inputType := reflect.TypeOf(input) fmt.Println("input type is : " ,inputType.Name()) inputValue := reflect.ValueOf(inputType).Elem() fmt.Println("input value is : " ,inputValue) for i := 0 ; i < inputType.NumMethod(); i++ { m := inputType.Method(i) fmt.Printf("%s: %v\n" ,m.Name,m.Type) } }func main () { num := 3.1415926525 reflectNum(num) fmt.Println("===================" ) user := User{ Id: 22 , Name: "Alexander liu" , Age: 19 , } DoFiledAndMethod(user) }
反射解析结构体标签tag 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" "reflect" )type resume struct { Name string `info:"name" doc:"我的名字"` Sex string `info:"sex"` }func findTag (str interface {}) { t := reflect.TypeOf(str).Elem() for i := 0 ; i < t.NumField(); i++ { tagString := t.Field(i).Tag.Get("info" ) tagdoc := t.Field(i).Tag.Get("doc" ) fmt.Println("info: " , tagString , " doc: " ,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 33 34 35 36 37 38 39 40 41 42 package mainimport ( "encoding/json" "fmt" )type Movie struct { Title string `json:"title"` Year int `json:"year"` Price int `json:"rmb"` Actors []string `json:"actors"` }func main () { movie := Movie{ Title: "Avengers" , Year: 2019 , Price: 142 , Actors: []string {"Iron Man" ,"Thor" ,"Hulk" ,"Vision" ,"Captain America" }, } jsonStr, err := json.Marshal(movie) if err!=nil { fmt.Println("json marshal error" ,err) return } fmt.Printf("jsonStr = %s\n" ,jsonStr) my_movie := Movie{} err = json.Unmarshal(jsonStr,&myMovie) if err != nil { fmt.Println("json unmarshal error" , err) return } fmt.Printf("%v\n" ,myMovie) }
jsonStr = {"title":"Avengers","year":2019,"rmb":142,"actors":["Iron Man","Thor","Hulk","Vision","Captain America"]}
可以看到这里的标签就是我们上面的tag中的标签哦!!!和Java相比,多了一种方式嗷!!!
GoRoutine 设计策略:
复用内核级线程
多线程,多进程解决了阻塞的问题,但是还是有问题!!!
M:N关系:
GMP:
G -> goroutine协程
P -> processor处理器
M -> thread线程(内核态线程)
整体结构:
自己的没了,从旁边的队列偷一个过来执行,偷取机制!!!
分离机制,一个携程卡死了,CPU里面进行M的切换,队列也发生改变,让阻塞的阻塞去吧,我继续干活了嗷!!!
利用并行
GOMAXPROCS限定P的个数 = CPU核数 / 2
抢占:
全局G队列
创建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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package mainimport ( "fmt" "time" )func newTask () { i := 0 for { i++ fmt.Printf("new Gouroutine : i = %d\n" ,i) time.Sleep(1 * time.Second) } }func main () { go newTask() go func () { defer fmt.Println("B.defer" ) fmt.Println("B" ) }() go func (a int , b int ) { fmt.Println(a + b) }(10 ,20 ) i := 0 for i != 5 { i ++ fmt.Printf(" main goroutine : i = %d\n" ,i) time.Sleep(1 * time.Second) } }
通信机制Channel:
make(chan Type, capacity)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func main () { c := make (chan int ) go func () { defer fmt.Println("goroutine结束" ) time.Sleep(time.Second*5 ) fmt.Println("goroutine正在执行" ) c <- 666 }() num := <- c fmt.Println("num = " ,num) fmt.Println("main goroutine 结束..." ) }
无缓冲Channel: 示意图:
上面的代码其实就是无缓冲的channel嗷
有缓冲Channel:
channel满了塞数据就会阻塞,channel空了取数据就会阻塞。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 < 4 ; i++ { c <- i fmt.Println("子go正在执行,发送的元素是:" ,i) } }() time.Sleep(2 *time.Second) for i := 0 ; i < 3 ; i++ { num := <-c fmt.Println("num = " ,num) } fmt.Println("main结束了嗷!!!" ) }
关闭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 func main () { c := make (chan int ) go func () { for i := 0 ; i < 5 ; i++ { c <- i } }() for { if data, ok := <- c;ok { fmt.Println(data) }else { break } } fmt.Println("Main Finished ... " ) }
因为子goroutine退出了,channel没有关闭,main在取数据的时候阻塞,又没有人叫它起来,gg了。
如果关闭了话!!!
Channel不像文件一样需要经常去关闭,只有当我们没有任何数据要发送了,才关闭channel
关闭channel后再发数据会引发panic,并且接收立即返回零值 。
关闭channel后,可以继续从channel接收数据
对于nil channel,无论收发都会被阻塞嗷!!!(比如声明了一个chan但是并没有make,就是nil咯orz)
Channel和range:
迭代操作Channel的时候我们使用range嗷!!!
1 2 3 for data := range c{ fmt.Println(data) }
非常好用嗷!!!有的话我就取,没有我就等,关了我就收!!!
Channel和select:
单流程下一个go只能监控一个channel的状态,select可以监控多个channel的状态!!!
select一般和for搭配一起使用嗷!!!
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 func fibonacci (c , quit chan int ) { x , y := 1 ,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) }
多路Channel的一个监控功能嗷!!!
GOMODULES 工作模式弊端
Go Modules的出现淘汰了GoPath,例如之前GOPATH要求项目全写在$GOPATH下,很恼火,很不自由嗷!!!
cmd中输入go env可以查看go的环境变量
GOPAHT的弊端:
没有版本控制的概念哈!!!无法指定拉取自己需要的版本的库之类的
无法同步一致第三方版本号
无法指定当前项目引用的第三方版本号
GoModules模式
go mod help
可以得到go mod的全部指令的帮助文档。
GO111MODULE
GOPROXY
这个本质上就是配置镜像源嗷!!!
注意这里七牛云后面还有个,direct
,或者表示如果在前面的仓库中找不到,就会回源到模块版本的源地址去找(比如Github,如果再找不到的话,就会报错了嗷!!!)
GOSUMDB
校验拉取的第三方库是否完整。设置了GOPROXY,我们就不用担心这个原来的设置是外网的库拉,这个GOPROXY就会帮助我们自动校验嗷!!!
GONOPROXY/GONOSUMDB/GOPRIVATE
通过设置一个GOPRIVATE即可嗷!!!
环境变量设置:
go env -w 环境变量名=相应参数
Go Modules初始化项目
步骤:
创建对应的文件夹
进入文件夹中执行go mod init 当前模块名称
后面导包的时候,项目的整体名称就是这个了嗷!!!
进入初始化好的go.mod文件看一眼
可以看到这个项目的module名称还有go的版本号
导入单个子模块:
直接找到对应的模块go get就可以拉下来了嗷!!!
其实可以不用手动down的,你直接跑项目的时候go mod就会帮助我们自动down下来我们需要的依赖了嗷!!!
下载整个模块:
go get github.com/aceld/zinx
这样就把对应的项目的包全部都拉下来了嗷!!!
跑一把:
导完之后发现当前目录下多了一个go.sum文件嗷!!!
而且当我们访问go mod的时候:
间接依赖,这里意味着,我只以来了这个包的子模块,不是整体依赖这个包嗷!!!
第一个h1 + hash,第二个是gomod + hash。表达一共就只有这两种方式哈!!!
h1 + hash:对于整个包所有文件做哈希,这是为了保证整个包全部文件的完整性。
gomod + hash:表示对于mod文件进行的Hash,确保hash没有问题。
$GOPATH/pkg
这个包中,有一个mod模块,里面记录了我们的所有的不同的module的依赖包,打开对应的module就能够看到我们在对应的module中下载的依赖包了嗷!!!
改变模块依赖关系
比如确定要使用某个版本的依赖,这个版本才有对应的依赖和资源。
第一:可以更改go mod中的版本依赖,改回某个老版本。
第二:使用指令进行版本替换:
你需要找到$GOPATH下对应的下载的包和对应的版本,然后进行replace替换操作嗷!!!
即时通信系统小项目
OnlineMap:记录在线用户
OnlineMap旁边的channel:进行广播channel
版本一:构建基础Server
建立server.go文件,写Server这个类,这个类包含三个大方法和一个重要的结构:
Server类型的定义
创建一个Server对象方法
启动一个Server服务方法
处理一个连接方法
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 53 package mainimport ( "fmt" "net" )type Server struct { Ip string Port int }func NewServer (ip string , port int ) *Server { server := &Server{ Ip: ip, Port: port, } return server }func (this *Server) Handler(conn net.Conn) { fmt.Println("连接建立成功" ) }func (this *Server) Start() { listener , err := net.Listen("tcp" ,fmt.Sprintf("%s:%d" ,this.Ip,this.Port)) if err != nil { fmt.Println("net.Listen error : " ,err) return } defer listener.Close() for { conn , err := listener.Accept() if err != nil { fmt.Println("listener accept error : " ,err) continue } go this.Handler(conn) } }
1 2 3 4 5 6 package mainfunc main () { server := NewServer("127.0.0.1" ,8888 ) server.Start() }
版本二:用户上线功能 还需要补充OnlineMap和Message这两个属性:
新增User.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 28 29 30 31 32 33 34 35 36 package mainimport "net" type User struct { Name string Addr string C chan string conn net.Conn }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 }func (this *User) ListenMessage() { for { msg := <- this.C this.conn.Write([]byte (msg+"\n" )) } }
修改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 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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 package mainimport ( "fmt" "net" "sync" )type Server struct { Ip string Port int OnlineMap map [string ]*User mapLock sync.RWMutex Message chan string }func NewServer (ip string , port int ) *Server { server := &Server{ Ip: ip, Port: port, OnlineMap: make (map [string ]*User), Message: make (chan string ), } return server }func (this *Server) BroadCast(user *User, msg string ) { sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg this.Message <- sendMsg }func (this *Server) ListenMessage() { for { msg := <- this.Message this.mapLock.Lock() for _, cli := range this.OnlineMap{ cli.C <- msg } this.mapLock.Unlock() } }func (this *Server) Handler(conn net.Conn) { user := NewUser(conn) this.mapLock.Lock() this.OnlineMap[user.Name] = user this.mapLock.Unlock() this.BroadCast(user,"已经上线" ) select { } }func (this *Server) Start() { listener , err := net.Listen("tcp" ,fmt.Sprintf("%s:%d" ,this.Ip,this.Port)) if err != nil { fmt.Println("net.Listen error : " ,err) return } defer listener.Close() go this.ListenMessage() for { conn , err := listener.Accept() if err != nil { fmt.Println("listener accept error : " ,err) continue } go this.Handler(conn) } }
版本三:用户消息广播机制
其实上面已经完成了相关的广播功能的编写了
我们接下来需要的就是编写对应的函数,把相应的用户消息放入我们的消息队列中即可
版本四:用户业务层封装
把代码进行合并,整理成函数嗷,是谁负责的,例如用户的登录和退出,就封装在哪个类的方法中嗷!!!!同时通过数据结构中的指针,把用户和Service串起来,方便我们的操作嗷!!!
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 func (this *User) Online() { this.server.mapLock.Lock() this.server.OnlineMap[this.Name] = this this.server.mapLock.Unlock() this.server.BroadCast(this, "已上线" ) }func (this *User) Offline() { this.server.mapLock.Lock() delete (this.server.OnlineMap, this.Name) this.server.mapLock.Unlock() this.server.BroadCast(this, "下线" ) }func (this *User) DoMessage(msg string ) { this.server.BroadCast(this, msg) }
上面就是重构后提取出来的用户的业务嗷!!!
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 (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 {} }
上面就是重构后提取出来的service的业务嗷!!!
以及把User和Server(正向一对一,反向一对多),分别使用指针和Map的方式将两者穿起来,方便我们后续的操作嗷!!!
版本五:在线用户查询
添加在用户的DoMessage方法中,对于用户输入的消息进行判断,如果为”who”,就返回所有用户信息即可嗷!!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func (this *User) SendMsg(msg string ) { this.conn.Write([]byte (msg)) }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) } this.server.mapLock.Unlock() } else { this.server.BroadCast(this, msg) } }
版本六:修改用户名
定义一下:例如:rename|兔子
就是当前用户名重命名为兔子!!!
和上面一样,都要使用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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 func (this *User) SendMsg(msg string ) { this.conn.Write([]byte (msg)) }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) } this.server.mapLock.Unlock() }else if len (msg) > 7 && msg[:7 ] == "rename|" { newName := strings.Split(msg,"|" )[1 ] _, ok := this.server.OnlineMap[newName] if ok { this.SendMsg("当前用户名被使用了!!!\n" ) }else { 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" ) } } else { this.server.BroadCast(this, msg) } }
版本七:超时强踢功能
首先应该记录一个用户的在线时间嗷!!!
用到了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 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 53 54 55 56 57 58 59 60 61 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 } }() for { select { case <-isLive: case <-time.After(time.Second * 10 ): user.SendMsg("你被踢了" ) close (user.C) conn.Close() return } } }
版本八:私聊功能
和之前一样,修改DOMESSAGE,类似于自己定义通信协议嗷!!!
例如:to|用户名|信息
这种方式,进行判断即可嗷!!!从全局的用户列表中,找到对应的用户,把他的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 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 53 54 55 56 57 58 59 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) } this.server.mapLock.Unlock() } else if len (msg) > 7 && msg[:7 ] == "rename|" { newName := strings.Split(msg, "|" )[1 ] _, ok := this.server.OnlineMap[newName] if ok { this.SendMsg("当前用户名被使用\n" ) } else { 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" ) } } else if len (msg) > 4 && msg[:3 ] == "to|" { remoteName := strings.Split(msg, "|" )[1 ] if remoteName == "" { this.SendMsg("消息格式不正确,请使用 \"to|张三|你好啊\"格式。\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) } else { this.server.BroadCast(this, msg) } }
版本九:客户端实现
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 package main import ( "flag" "fmt" "io" "net" "os" ) type Client struct { ServerIp string ServerPort int Name string conn net.Conn flag int } func NewClient (serverIp string , serverPort int ) *Client { client := &Client{ ServerIp: serverIp, ServerPort: serverPort, flag: 999 , } conn, err := net.Dial("tcp" , fmt.Sprintf("%s:%d" , serverIp, serverPort)) if err != nil { fmt.Println("net.Dial error:" , err) return nil } client.conn = conn return client } func (client *Client) DealResponse() { io.Copy(os.Stdout, client.conn) } func (client *Client) menu() bool { var flag int fmt.Println("1.公聊模式" ) fmt.Println("2.私聊模式" ) fmt.Println("3.更新用户名" ) fmt.Println("0.退出" ) fmt.Scanln(&flag) if flag >= 0 && flag <= 3 { client.flag = flag return true } else { fmt.Println(">>>>请输入合法范围内的数字<<<<" ) return false } } func (client *Client) SelectUsers() { sendMsg := "who\n" _, err := client.conn.Write([]byte (sendMsg)) if err != nil { fmt.Println("conn Write err:" , err) return } } func (client *Client) PrivateChat() { var remoteName string var chatMsg string client.SelectUsers() fmt.Println(">>>>请输入聊天对象[用户名], exit退出:" ) fmt.Scanln(&remoteName) for remoteName != "exit" { fmt.Println(">>>>请输入消息内容, exit退出:" ) fmt.Scanln(&chatMsg) for chatMsg != "exit" { if len (chatMsg) != 0 { sendMsg := "to|" + remoteName + "|" + chatMsg + "\n\n" _, err := client.conn.Write([]byte (sendMsg)) if err != nil { fmt.Println("conn Write err:" , err) break } } chatMsg = "" fmt.Println(">>>>请输入消息内容, exit退出:" ) fmt.Scanln(&chatMsg) } client.SelectUsers() fmt.Println(">>>>请输入聊天对象[用户名], exit退出:" ) fmt.Scanln(&remoteName) } } func (client *Client) PublicChat() { var chatMsg string fmt.Println(">>>>请输入聊天内容,exit退出." ) fmt.Scanln(&chatMsg) for chatMsg != "exit" { if len (chatMsg) != 0 { sendMsg := chatMsg + "\n" _, err := client.conn.Write([]byte (sendMsg)) if err != nil { fmt.Println("conn Write err:" , err) break } } chatMsg = "" fmt.Println(">>>>请输入聊天内容,exit退出." ) fmt.Scanln(&chatMsg) } } func (client *Client) UpdateName() bool { fmt.Println(">>>>请输入用户名:" ) fmt.Scanln(&client.Name) sendMsg := "rename|" + client.Name + "\n" _, err := client.conn.Write([]byte (sendMsg)) if err != nil { fmt.Println("conn.Write err:" , err) return false } return true } func (client *Client) Run() { for client.flag != 0 { for client.menu() != true { } switch client.flag { case 1 : client.PublicChat() break case 2 : client.PrivateChat() break case 3 : client.UpdateName() break } } } var serverIp string var serverPort int func init () { flag.StringVar(&serverIp, "ip" , "127.0.0.1" , "设置服务器IP地址(默认是127.0.0.1)" ) flag.IntVar(&serverPort, "port" , 8888 , "设置服务器端口(默认是8888)" ) } func main () { flag.Parse() client := NewClient(serverIp, serverPort) if client == nil { fmt.Println(">>>>> 链接服务器失败..." ) return } go client.DealResponse() fmt.Println(">>>>>链接服务器成功..." ) client.Run() }
Init的使用,Init是一个非常有意思的东西,可以指定某些参数的值为定值,同时init()会在main之前执行嗷!!!
Golang生态拓展介绍
Web框架:
微服务框架:
容器编排:
服务发现:
存储引擎:
静态建站:
中间键:
爬虫框架: