7. go并发:同步原语,用sync包控制并发

go在并发的时候,可能会出现多个协程同时访问一个资源的时候,这就出现了资源竞争。也可能出现协程还在运行,但是主程序却退出了的情况,这是缺少控制导致的。 用sync包,可以方便的控制资源的访问,也可以方便实现阻塞等待,让协程执行完毕再退出程序,或者执行下一步。 资源竞争 在同一个goroutine中,如果分配的内存没有被其他的goroutine访问,只在该goroutine中使用,则不存在资源竞争问题 如果同一块内存被多个goroutine同时访问,就会产生不知道谁先访问,也无法预料最后结果的情况,这就是资源竞争: // 共享的资源 var sum = 0 func main(){ for i := 0; i < 100; i++ { go add(10) } // goroutine 不会阻塞下面的代码,此处Sleep一下,防止main goroutine直接退出 // 而导致未完成的goroutine也被终止 time.Sleep(time.Second * 2) fmt.Println("Sum is:", sum) } func add(i int) { sum += i } 上述例子中,sum变量为共享的资源,程序运行的过程中会发生资源竞争。 使用 go build、go run、go test 这些 Go 语言工具链提供的命令时,添加 -race 标识可以帮你检查 Go 语言代码是否存在资源竞争 同步原语 sync....

February 19, 2022 · 3 min · LingZihuan

6. go并发:Goroutines和Channels的声明和使用

并发,就是让程序在同一时刻做多件事情。 go语言天生自带并发属性,使得并发编程十!分!方!便!,我们只需要 go 函数名() 即可!\(^o^)/ 进程和线程 进程 程序启动时,系统会为其创建一个进程 线程 是进程的执行空间,一个进程可以包含多个线程,线程被操作系统调度执行 一个程序启动,对应的进程会被创建,同时也会创建一个线程(主线程),主线程结束,整个程序也就退出了。 我们可以从主线程创建其他的子线程,这就是多线程并发 协程 Goroutine goroutine比线程更加轻盈,被Go runtime调度。 启动协程: go function() // 这里启动了两个goroutine, 一个是用go关键字触发的,另一个是 main goroutine(主线程) func main(){ go fmt.Println("Hello goroutine.") fmt.Println("Main goroutine.") time.Sleep(time.Second) } Channel 多个goroutine之间,使用 channel进行通信 声明一个channel // 直接使用 make 创建一个channel,接受的数据类型是string ch := make(chan string) // 一个channel的操作只有两种: // - 发送,向chan中发送值: chan<- // - 接受,从chan中获取值: <-chan demo func main(){ ch := make(chan string) go func(){ fmt....

February 18, 2022 · 2 min · LingZihuan

5. go基础:通过 error/deffer/panic 处理错误

正确处理错误,可以让程序更加稳定。error是一个错误接口,一般来说,error是不会影响到整个程序运行的,我们可以自行处理。 而panic是会导致程序直接崩溃退出的,我们也可以用 go自带的recover()来恢复panic,在程序崩溃前进行资源的释放工作。 错误 error接口 error接口只有一个Error方法,返回具体的错误信息(string) type error interface { Error() string } 一个字符串转整数的例子: func main(){ // try to conver alpha "a" to integer i, err := strconv.Atoi("a") if err != nil { fmt.Println(err) } else { fmt.Println(i) } } 尝试将字符 a 转换成为 整数,显然是不成功的,因此返回了err 一般而言,error 接口用于当方法或者函数执行遇到错误时进行返回,而且是第二个返回值。通过这种方式,可以让调用者自己根据错误信息决定如何进行下一步处理。 error 工厂函数 除了可以使用其他函数,自己定义的函数也可以返回错误信息给调用者 自定义error 自定义error就是先自定义一个新的类型,然后实现error接口 // 自定义一个结构体,携带错误码和具体的错误信息 type commonError struct { errorCode int // 错误码 errorMsg string // 错误信息 } // 实现error接口 func (ce *commonError) Error() string { return ce....

February 17, 2022 · 2 min · LingZihuan

3. go基础:函数和方法

函数和方法,大大提高了我们的代码复用率,也提高了代码的可读性。 go语言的函数和方法的定义十分简单,直接使用 func 关键字即可。 不同于python、C++等有类的概念,而这些类中,又有自己的方法,或者父类的方法。go语言中,我们可以定义一个数据结构,然后给这个数据结构实现自己的方法,同样可以实现【类】的效果,通过组合,同一个结构体中,也可以使用或者覆写其他结构体的方法。 函数 函数的组成: 关键字 func 函数名 funcName 参数 params 返回值(返回值类型) 函数体 func funcName(params) result { // body } // 示例1:普通的函数定义 func sum(a int, b int) int { return a + b } // 示例2:参数表的类型合并,(a int, b int) -> (a, b int) func sum2(a, b int) int { return a + b } // 示例3:多值返回,用 () 包裹起来 func sum3(a, b int) (int, error) { if a < 0 || b < 0 { return 0, errors....

February 15, 2022 · 2 min · LingZihuan

4. go基础:结构体和接口

接口是一种聚合类型,结构体是和调用方的一种约定,有点抽象类的意思。:) 结构体 结构体定义 结构体是一种聚合类型,里面可以包含任意类型的值,这些值就是我们定义的结构体成员,也称为字段 在go语言中,要定义一个结构体,需要使用 type+struct 关键字组合。 // 定义一个代表【人】的结构体 type person struct { name string // 名称 age uint // 年龄 } 结构体的成员字段并不是必需的,也可以一个字段都没有,这种结构体成为空结构体。 type s struct {} 结构体声明使用 // 使用var声明一个person变量,未初始化,里面的值为各自变量的零值 var p person // 可以使用结构体字面量初始化的方式 p2 := person{"Mike", 10} // 第一个值为 name,第二个值为age,与结构体字段定义顺序有关 // 可以指定字段名初始化,不按定义顺序 p3 := person{age: 10, name: "Mike"} 字段结构体 type address struct { province string city string } // 结构体的字段可以是任意类型,也可以是自定义的结构体 type person struct { name string age uint addr address } // 初始化 p := person{ name: "Mike", age: 10, addr: address{ province: "Guandong", city: "Maoming", }, } fmt....

February 15, 2022 · 3 min · LingZihuan