依赖管理
包管理
注意:一个包对应一个目录,一个目录下不同文件之间可以相互公开,只有不同的包之间需要大写使其公开。
使用import之后编译运行一个文件就会连带着编译其import的包
GOPATH 一个目录包括三个子目录
src,不同的包的源代码
pkg,编译后的类库
bin,编译后的可执行程序
go install
命令将编译之后的结果文件:.a应用包或可执行文件导入到pkg或bin中
go get
命令将源码直接导入src中然后执行 go install
Go Modules gopath改进之后通过go.mod文件进行包的管理
通过命令 go mod init [module name]
生成go.mod文件
go.mod文件样例:
1 2 3 module name go 1.20 require github.com/longjoy/micro-go -book v0.0 .1
可以通过 go mod tidy
命令进行依赖的更新
最终下载的第三方库保存在GOMODCACHE中,即GOPATH/pkg/mod中
反射基础
一般不会自己写反射代码,理解原理即可
提供运行时对代码的访问和修改的能力
两个概念
Type:主要表示被反射变量的类型信息。
Value:表示被反射变量的类型信息
go语言反射的实现主要位于reflect包中
reflect.Type类型对象 通过 typeofname := reflect.Typeof(type name)
方法获取一个变量的类型信息,存到一个reflect.Type的类型的变量中。
通过typeofname.kind()获得type name的种类(struct 等等)而typeofname是指哪个包里定义的类型packagename.typename
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 type Kind uintconst ( Invalid Kind = iota Bool Int Int8 Int16 Int32 Int64 Uint Uint8 Uint16 Uint32 Uint64 Uintptr Float32 Float64 Complex64 Complex128 Array Chan Func Interface Map Ptr Slice String Struct UnsafePointer )
类型对象reflect.StructField和reflect.Method StructField reflect中存在structfield类型用于存储:结构体内字段的类型信息
以上述通过typeof(type name)方法获得的一个结构体类型对象为接收器可以使用以下三个方法获得该结构体对象下属的字段的structfield类型信息:
1 2 3 4 5 6 NumField() int Field(i int ) StructField FieldByName(name string ) (StructField, bool )
StructField中的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 type StructField struct { Name string Type Type Tag StructTag Offset uintptr Index []int Anonymous bool }
Method reflect中还存在Method类型用于存储:接口下方法的方法类型对象
go语言中可以声明一个接口变量,并赋值以接收器:var person Person = &Hero{}
,其中Person是一个接口,Hero是一个结构体。
通过相同的方法 typeOfPerson := reflect.Typeof(person)
可以获得一个接口Person的类型对象。
而以接口类型对象为接收器存在以下方法来获取Method类型对象
1 2 3 4 5 6 Method(int ) Method MethodByName(string ) (Method, bool ) NumMethod() int
Method内的基本信息
1 2 3 4 5 6 7 8 9 10 type Method struct { Name string Type Type Func Value Index int }
Method.Type为func(接收器,剩余的原方法参数)
reflect.Value反射值对象 使用上述的Type类型对象,我们只能查看类型信息,不能对变量的值进行查看和修改,所以有了reflect.Value反射值对象。
可以通过reflect.ValueOf获取反射变量的信息Value,通过Value对变量的值进行查看和修改。
获取变量值 通过Value.Interface()方法获取变量的值
可以使用reflect.New(类型对象)创建一个相同类型的新变量,值以Value对象的形式返回。
改变变量值 对变量的修改可以通过方法Value.Set方法实现
例子如下:
1 2 3 4 name := "小明" valueOfName := reflect.ValueOf(&name) valueOfName.Elem().Set(reflect.ValueOf("小红" )) fmt.Println(name)
注意:由于Value的值是原变量值的拷贝,即使ValueOf(&name),也只是指向name指针的拷贝,要寻址到原变量要使用#Elem方法。
可以通过方法#CanAddr方法判断是否可以寻址
结构体value 对于结构体的反射值对象,可以通过Value.CanSet判断某字段是否公开可以改变,结构体的value同时也有类似type的一些方法:#NumField,#FieldByIndex,#FieldByName来获取字段的value
反射接口方法调用 使用函数 func (v value) Call(in []value) []Value
1 2 3 4 5 6 7 8 9 10 var person Person = &Hero{ Name:"小红" , Speed:"100" , } valueOfPerson := reflect.ValueOf(person) sayHelloMethod := valueOfPerson.MethodByName("sayHello" ) sayHelloMethod.Call([]reflect.Value{reflect.ValueOf("小张" )})
也可以使用上边type中的Method.func调用,但要将接收器作为第一个参数传入
并发模型
并发与并行
并发:同一时间段内,多条指令在CPU上同时执行
并行:同一时刻上,多条指令在CPU上同时执行
CSP并发模型 go语言实现两种并发模型
线程与锁并发模型:依赖于共享内存,程序出错不易排查
CSP(通信顺序进程模型):有两个关键概念
并发实体:即执行线程,他们之间相互独立并发执行
通道:并发实体之间使用通道发送信息
极易导致死锁
常见线程模型 线程是操作系统能够调度的最小单位分为用户线程和内核线程
用户线程:由用户空间的代码创建、管理和销毁,调度由用户空间的线程库完成
内核线程:由操作系统管理和调度,线程切换需要cpu切换为内核态
用户线程无法被操作系统感知,用户线程所属的进程或者内核线程才能被操作系统直接调度。
用户级线程模型
一个进程包含多个用户线程,对应一个内核线程
缺点:一个用户线程阻塞导致整个进程失去时间片
内核级线程模型
每个用户线程对应一个内核线程
缺点:线程切换从用户态到内核态资源消耗大
两级线程模型
上述两个模型相结合:一个进程对应多个内核线程,由进程中的调度器决定进程内的线程如何与申请的内核线程相对应。
GMP线程模型 go语言中的MPG线程模型对两级线程模型进行改进:
M:Machine,一个Machine对应一个内核线程,相当于内核线程在go语言进程中的映射
P:Processor,go代码片段执行所需的上下文环境,用户代码逻辑处理器
G:Goroutine,go代码片段的封装,是一种轻量级的用户线程
如上图:M,P共同构成了一个基本的运行环境,此时G0中的代码处于运行的状态,右边G队列处于待执行的状态。
当没有足够的M来和P组合为G提供运行环境时,Go语言会创建新的M,在很多时候M的数量可能比P多。在单个Go语言进程中,P的最大数量决定了程序的并发规模,且P的最大数量由程序决定,可以通过修改环境变量GOMAXPROCS和调用函数runtime.GOMAXPROCS来设定P的最大值。
并发实践
协程goroutine goroutine是go语言中的轻量级进程,在运行的时候由runtine管理,我们编写main函数也是运行在goroutine之上,可以通过 go 表达式语句
来启动一个新的goroutine
表达式语句可以是内建函数,也可以是自定义的方法和函数(命名或匿名都可)
注意:go语言不同的goroutine间的代码次序并不代表真正的执行顺序(不清楚真正的调度顺序),主goroutine结束,其创建的goroutine如果还没有执行那么会被销毁。
对比OS线程
os线程:固定栈内存:2MB
goroutine:栈内存不固定,从2KB到1GB
示例 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" "time" ) func main () { arr := []int {1 , 2 , 3 , 4 } for _, v := range arr { go func () { fmt.Printf("%d\t" , v) }() } time.Sleep(time.Duration(1 ) * time.Second) fmt.Println() for _, v := range arr { go func (value int ) { fmt.Printf("%d\t" , value) }(v) } time.Sleep(time.Duration(1 ) * time.Second) fmt.Println() }
goroutine协程关闭
通道channel channel声明:
1 2 3 4 var channelName chan T channelName <- val val,ok := channel ch := make (chan T,sizeOfChan)
注意:创建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 26 27 28 package mainimport ( "bufio" "fmt" "os" ) func printInput (ch chan string ) { for val := range ch { if val == "EOF" { break } fmt.Printf("Input is %s\n" ,val) } } func main () { ch := make (chan string ) go printInput(ch) scanner := bufio.NewScanner(os.Stdin) for scanner.Scan(){ val := scanner.Text() ch <- val if val == "EOF" { fmt.Println("End the game!" ) break } } defer close (ch) }
select 使用select可以从多个channel中读取数据:
1 2 3 4 5 6 7 8 select {case val := <- ch1: ... case val := <- ch2: ... case <- time.After(2 * time.Second): ... }
sync同步包 互斥锁:Mutex 1 2 3 var lock sync.Mutexlock.Lock() lock.Unlock()
读写锁:RWMutex 接口:(允许多读单写,读写互斥)
1 2 3 4 5 6 7 8 func (rw *RWMutex) Lock()func (rw *RWMutex) Unlock()func (rw *RWMutex) RLock()func (rw *RWMutex) RUnlock()
WaitGroup(并发等待组) 接口:
1 2 3 4 5 6 func (wg *WaitGroup) Add(delta int )func (wg *WaitGroup) Done()func (wg *WaitGroup) Wait()
map(并发安全字典) 原生的字典map多个goroutine同时添加key-value的时候可能会发生数据的丢失
go语言中有sync.Map提供以下接口:
1 2 3 4 5 6 7 8 9 10 func (m *Map) Load(key interface {}) (value interface {},ok bool )func (m *Map) Store(key, value interface {})func (m *Map) LoadOrStore(key, value interface {}) (actual interface {},loaded bool )func (m *Map) Delete(key interface {})func (m *Map) Range(f func (key,value interface {}) bool )
标准库 参考:go标准库中文文档
OS 读文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 fileObj,err := os.Open("./main.go" ) defer fileObj.Close()var tmp = make {[]byte ,128 }n,err := fileObj.Read(tmp) if err == io.EOF{ return } reader := bufio.NewReader(fileObj) line,err := reader.ReadString('\n' ) ret,err := ioutil.ReadFile("./main.go" )
写文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 fileObj,err := os.OpenFile("./log.txt" ,os.O_APPEND|os.O_CREATE,0644 ) defer fileObj.Close()fileObj.Write([]byte ("xxx" )) fileObj.WriteString("xxx" ) writer := bufio.NewWriter(fileObj) writer.WriteString("xxx" ) writer.Flush() err := ioutil.WriteFile("./log.txt" ,[]byte (str),0666 )
Time 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 now := time.Now() now.Year() now.Unix() nowTime := time.Unix(now,0 ) now.Add(24 * time.Hour) timer := time.Tick(time.Second) now.Format("2006-01-02" ) now.Format("2006/01/02 15:04:05" ) timeObj := time.Parse("2006-01-02" ,"2004-01-01" ) time.Duration(100 )
Strconv 1 2 3 4 5 6 7 8 strconv.ParseInt(str,10 ,64 ) strconv.Atoi(str) strconv.Itoa(i) strconv.ParseBool("true" ) ...
Context Context 包提供上下文机制在 goroutine 之间传递 deadline、取消信号(cancellation signals)或者其他请求相关的信息。
1 2 3 4 5 6 7 8 9 10 type Context interface { Done() <-chan struct {} Deadline() (deadline time.Time, ok bool ) Err() error Value(key interface {}) interface {} }
创建根context 根 context 不会被 cancel。这两个方法只能用在最外层代码中,比如 main 函数里。
1 2 3 4 context.Background() context.TODO()
派生context 1 2 3 4 func WithCancel (parent Context) (ctx Context, cancel CancelFunc)func WithDeadline (parent Context, d time.Time) (Context, CancelFunc)func WithTimeout (parent Context, timeout time.Duration) (Context, CancelFunc)func WithValue (parent Context, key, val interface {}) Context
示例 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 package mainimport ( "context" "fmt" "time" ) func sleepRandom_1 (stopChan chan struct {}) { i := 0 for { time.Sleep(1 * time.Second) fmt.Printf("This is sleep Random 1: %d\n" , i) i++ if i == 5 { fmt.Println("cancel sleep random 1" ) stopChan <- struct {}{} break } } } func sleepRandom_2 (ctx context.Context) { i := 0 for { time.Sleep(1 * time.Second) fmt.Printf("This is sleep Random 2: %d\n" , i) i++ select { case <-ctx.Done(): fmt.Printf("Why? %s\n" , ctx.Err()) fmt.Println("cancel sleep random 2" ) return default : } } } func main () { ctxParent, cancelParent := context.WithCancel(context.Background()) ctxChild, _ := context.WithCancel(ctxParent) stopChan := make (chan struct {}) go sleepRandom_1(stopChan) go sleepRandom_2(ctxChild) select { case <- stopChan: fmt.Println("stopChan received" ) } cancelParent() for { time.Sleep(1 * time.Second) fmt.Println("Continue..." ) } }