go语言笔记2
上接Go语言学习笔记(一)
11 Go错误处理
11.1 nil
函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息:
package main
import (
"errors"
"fmt"
"math"
)
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}else {
return math.Sqrt(f),nil;
}
}
func main() {
result, err:= Sqrt(-1)
fmt.Println(result,err)
}
在上面的例子中,我们在调用Sqrt的时候传递的一个负数,然后就得到了non-nil的error对象,将此对象与nil比较,结果为true,所以fmt.Println(fmt包在处理error时会调用Error方法)被调用,以输出错误。
11.2 接口实现
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
error类型是一个接口类型,这是它的定义:
type error interface {
Error() string
}
由于在9中说过接口实现不需要类似implements的关键字来实现,所以直接实现对于的接口方法即可。
package main
import (
"fmt"
)
// 定义一个 DivideError 结构
type DivideError struct {
dividee int
divider int
}
// 实现 `error` 接口
func (de *DivideError) Error() string {
strFormat := `
Cannot proceed, the divider is zero.
dividee: %d
divider: 0
`
return fmt.Sprintf(strFormat, de.dividee)
}
// 定义 `int` 类型除法运算的函数
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
if varDivider == 0 {
dData := DivideError{
dividee: varDividee,
divider: varDivider,
}
errorMsg = dData.Error()
return
} else {
return varDividee / varDivider, ""
}
}
func main() {
// 正常情况
if result, errorMsg := Divide(100, 10); errorMsg == "" {
fmt.Println("100/10 = ", result)
}
// 当被除数为零的时候会返回错误信息
if _, errorMsg := Divide(100, 0); errorMsg != "" {
fmt.Println("errorMsg is: ", errorMsg)
}
}
11.3 panic与recover
panic 与 recover,一个用于主动抛出错误,一个用于捕获panic抛出的错误。
1.引发panic有两种情况,一是程序主动调用,二是程序产生运行时错误,由运行时检测并退出。
2.发生panic后,程序会从调用panic的函数位置或发生panic的地方立即返回,逐层向上执行函数的defer语句,然后逐层打印函数调用堆栈,直到被recover捕获或运行到最外层函数。
3.panic不但可以在函数正常流程中抛出,在defer逻辑里也可以再次调用panic或抛出panic。defer里面的panic能够被后续执行的defer捕获。
4.recover用来捕获panic,阻止panic继续向上传递。recover()和defer一起使用,但是defer只有在后面的函数体内直接被掉用才能捕获panic来终止异常,否则返回nil,异常继续向外传递。
示例:多个panic只会捕捉最后一个
package main
import "fmt"
func main(){
defer func(){
if err := recover() ; err != nil {
fmt.Println(err)
}
}()//这里的()就是在调用匿名函数
defer func(){
panic("three")
}()
defer func(){
panic("two")
}()
panic("one")
}
输出:由于defer将函数链接后栈式执行,所以捕捉到了最早定义的three。
输出:three
示例二:
package main
import (
"fmt"
)
func main() {
GO()
PHP()
PYTHON()
}
//Go语言追求简洁优雅,所以,Go语言不支持传统的 try…catch…finally 这种异常,因为Go语言的设计者们认为,
// 将异常与控制结构混在一起会很容易使得代码变得混乱。因为开发者很容易滥用异常,甚至一个小小的错误都抛出一个异常。
// 在Go语言中,使用多值返回来返回错误。不要用异常代替错误,更不要用来控制流程。在极个别的情况下,也就是说,遇到真正的异常的
// 情况下(比如除数为0了)。才使用Go中引入的Exception处理:defer, panic, recover。
//Go没有异常机制,但有panic/recover模式来处理错误
//Panic可以在任何地方引发,但recover只有在defer调用的函数中有效
func GO() {
fmt.Println("1. 我是GO,现在没有发生异常,我是正常执行的。")
}
func PHP() {
// 必须要先声明defer,否则不能捕获到panic异常,也就是说要先注册函数,后面有异常了,才可以调用
defer func() {
if err := recover(); err != nil {
fmt.Println("2. 终于捕获到了panic产生的异常:", err) // 这里的err其实就是panic传入的内容
fmt.Println("3. 我是defer里的匿名函数,我捕获到panic的异常了,我要recover,恢复过来了。")
}
}() //注意这个()就是调用该匿名函数的,不写会报expression in defer must be function call
// panic一般会导致程序挂掉(除非recover) 然后Go运行时会打印出调用栈
//但是,关键的一点是,即使函数执行的时候panic了,函数不往下走了,运行时并不是立刻向上传递panic,而是到defer那,
// 等defer的东西都跑完了,panic再向上传递。所以这时候 defer 有点类似 try-catch-finally 中的 finally。
// panic就是这么简单。抛出个真正意义上的异常。
panic("4. 我是PHP,我要抛出一个异常了,等下defer会通过recover捕获这个异常,捕获到我时,在PHP里是不会输出的," +
"会在defer里被捕获输出,然后正常处理,使后续程序正常运行。但是注意的是,在PHP函数里,排在panic后面的代码也不会执行的。")
fmt.Println("5. 我是PHP里panic后面要打印出的内容。但是我是永远也打印不出来了。因为逻辑并不会恢复到panic那个点去," +
"函数还是会在defer之后返回,也就是说执行到defer后,程序直接返回到main()里,接下来开始执行PYTHON()")
}
func PYTHON() {
fmt.Println("6. 我是PYTHON,没有defer来recover捕获panic的异常,我是不会被正常执行的。")
}
输出:
1. 我是GO,现在没有发生异常,我是正常执行的。
2. 终于捕获到了panic产生的异常: 4. 我是PHP,我要抛出一个异常了,等下defer会通过recover捕获这个异常,捕获到我时,在PHP里是不会输出的,会在defer里被捕获输出,然后正常处理,使后续程序正常运行。但是注意的是,在PHP函数里,排在panic后面的代码也不会执行的。
3. 我是defer里的匿名函数,我捕获到panic的异常了,我要recover,恢复过来了。
6. 我是PYTHON,没有defer来recover捕获panic的异常,我是不会被正常执行的。
12 Go并发
12.1 goroutine
Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
goroutine 语法格式:
go f(x, y, z)
f(x, y, z)
示例:
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
//这里是为了让主协程进行休眠,使得go say()拥有足够的时间得以在主协程退出之前执行。
time.Sleep(100 * time.Millisecond)
fmt.Print(s)
}
}
func main() {
go say("world ")
say("hello ")//两个gouroutine(线程?)在执行
}
12.2 通道
通道见Go语言学习笔记(一)第4节。
12.3 协程
1. 内存消耗方面
每个 goroutine (协程) 默认占用内存远比 Java 、C 的线程少。
goroutine:2KB
线程:8MB
2. 线程和 goroutine 切换调度开销方面
线程/goroutine 切换开销方面,goroutine 远比线程小
线程:涉及模式切换(从用户态切换到内核态)、16个寄存器、PC、SP...等寄存器的刷新等。
goroutine:只有三个寄存器的值修改 - PC / SP / DX.
12.4 协程并发
协程(coroutine)是Go语言中的轻量级线程实现,由Go运行时(runtime)管理。
在一个函数调用前加上go关键字,这次调用就会在一个新的goroutine中并发执行。需要注意的是,如果这个函数有返回值,那么这个返回值会被丢弃(所以不能将go应用在赋值语句中)。
12.4.1 Go关键字的疑问
示例代码:
package main
import (
"fmt"
)
func hello() {
fmt.Println("Hello world goroutine")
}
func main() {
go hello()
fmt.Println("main function")
}
结果如下:
这里可以看到,屏幕并没有输出Hello world goroutine。
main 函数在单独的协程中运行,这个协程称为主协程。
当创建一个Go协程时,创建这个Go协程的语句立即返回。与函数不同,程序流程不会等待Go协程结束再继续执行。程序流程在开启Go协程后立即返回并开始执行下一行代码,忽略Go协程的任何返回值。
在主协程存在时才能运行其他协程,主协程终止则程序终止,其他协程也将终止。
11.4.2 Channel的解决方法
信道(Channel)可以被认为是协程之间通信的管道。与水流从管道的一端流向另一端一样,数据可以从信道的一端发送并在另一端接收。
特性
通过信道发送和接收数据默认是阻塞的。这是什么意思呢?当数据发送给信道后,程序流程在发送语句处阻塞,直到其他协程从该信道中读取数据。同样地,当从信道读取数据时,程序在读取语句处阻塞,直到其他协程发送数据给该信道。
信道的这种特性使得协程间通信变得高效,而不是向其他编程语言一样,显式的使用锁和条件变量来达到此目的
根据这个特性,我们可以进一步来避免掉有锁编程。
package main
import (
"fmt"
)
func hello(done chan bool) {
fmt.Println("Hello world goroutine")
done <- true
}
func main() {
done := make(chan bool)
go hello(done)
<-done
fmt.Println("main function")
}
在上面的程序中,我们在第 12 行定义了一个 bool 类型的信道 done,然后将它作为参数传递给 hello 协程。在第 14 行,我们从信道 done 中读取数据。程序将在这一行被阻塞直到其他协程向信道 done 里写入数据,在未读取到数据之前程序将在这一行一直等待而不会执行下一行语句。因此这里消除了在原程序中使用 time.Sleep 来阻止主协程退出的必要。
关于Go的基础知识就初步写到这里,没有涉及的点应该在于defer,这个后续有时间再修改吧。最近的工作接触到很多比较有意思的框架和语言,借用Go社区老哥的一句话,不同的语言所做的工作可能是不一样的,比如你搞java,可能更多的是企业级开发,大数据,你搞c可能更多是智能家居,网络通信,你搞php可能是网站建设,你搞go可能是云计算,所以换一门语言有时候不仅仅是换一门语言,换的是你的未来。我了解到Go是因为InfluxDB和docker,InfluxDB确实性能强悍,docker有多火热更不用说。Go的各种特性确实是适合云的,https://cloud.tencent.com/developer/article/1144911,但是也有自己的短板,go对泛型的支持不够,以及缺少像python一样丰富的库,诸位看官仁者见仁,智者见智。
---------------------
作者:暗焰之珩
来源:CSDN
原文:https://blog.csdn.net/weixin_42348333/article/details/86759950
版权声明:本文为博主原创文章,转载请附上博文链接!
go语言笔记2的更多相关文章
- R语言笔记
R语言笔记 学习R语言对我来说有好几个地方需要注意的,我觉得这样的经验也适用于学习其他的新的语言. 语言的目标 我理解语言的目标就是这个语言是用来做什么的,为什么样的任务服务的,也就是设计这个语言的动 ...
- R语言笔记4--可视化
接R语言笔记3--实例1 R语言中的可视化函数分为两大类,探索性可视化(陌生数据集,不了解,需要探索里面的信息:偏重于快速,方便的工具)和解释性可视化(完全了解数据集,里面的故事需要讲解别人:偏重全面 ...
- Scala语言笔记 - 第一篇
目录 Scala语言笔记 - 第一篇 1 基本类型和循环的使用 2 String相关 3 模式匹配相关 4 class相关 5 函数调用相关 Scala语言笔记 - 第一篇 最近研究了下scala ...
- Go 语言笔记
Go 语言笔记 基本概念 综述 Go 语言将静态语言的安全性和高效性与动态语言的易开发性进行有机结合,达到完美平衡. 设计者通过 goroutine 这种轻量级线程的概念来实现这个目标,然后通过 ch ...
- 014-预处理指令-C语言笔记
014-预处理指令-C语言笔记 学习目标 1.[掌握]枚举 2.[掌握]typedef关键字 3.[理解]预处理指令 4.[掌握]#define宏定义 5.[掌握]条件编译 6.[掌握]static与 ...
- 013-结构体-C语言笔记
013-结构体-C语言笔记 学习目录 1.[掌握]返回指针的函数 2.[掌握]指向函数的指针 3.[掌握]结构体的声明 4.[掌握]结构体与数组 5.[掌握]结构体与指针 6.[掌握]结构体的嵌套 7 ...
- 011-指针(上)-C语言笔记
011-指针(上)-C语言笔记 学习目标 1.[掌握]字符串常用函数 2.[掌握]指针变量的声明 3.[掌握]指针变量的初始化 4.[掌握]函数与指针 5.[掌握]指针的数据类型 6.[掌握]多级指针 ...
- 010-字符串-C语言笔记
010-字符串-C语言笔记 学习目标 1.[掌握]二维数组的声明和初始化 2.[掌握]遍历二维数组 3.[掌握]二维数组在内存中的存储 4.[掌握]二维数组与函数 5.[掌握]字符串 一.二维数组的声 ...
- 009-数组-C语言笔记
009-数组-C语言笔记 学习目标 1.[掌握]数组的声明 2.[掌握]数组元素的赋值和调用 3.[掌握]数组的初始化 4.[掌握]数组的遍历 5.[掌握]数组在内存中的存储 6.[掌握]数组长度计算 ...
- 008-进制-C语言笔记
008-进制-C语言笔记 学习目标 1.[掌握]include预处理指令 2.[掌握]多文件开发 3.[了解]认识进制 4.[掌握]进制之间的互相转换 5.[掌握]原码,反码,补码 6.[掌握]位运算 ...
随机推荐
- jsp标签${fn:contains()}遇到问题记录
在jsp页面要实现这样一个功能,列表的某一列字段要显示的数据,是从后台的一个列表中获取的,数据库里面该列存储的方式是 类似 1,2,3 这样的 主键id数据.显示的时候要根据id显示名称,如果是多个 ...
- 实现CI/CDk8s高可用集群搭建总结以及部署API到k8s
实现CI/CD(Centos7.2)系列二:k8s高可用集群搭建总结以及部署API到k8s 前言:本系列博客又更新了,是博主研究很长时间,亲自动手实践过后的心得,k8s集群是购买了5台阿里云服务器部署 ...
- JS系列:函数function
### 函数 function > 在js中函数就是一个方法(一个功能体)基于这个函数,一般是为了实现某个功能. ``` var total =10; total+=10; total=tota ...
- Windows 7 下使用gitblit + git 搭建小组内文件版本控制环境
一.GitBlit下载及配置 使用前先看下GitBlit的百科介绍,很简洁:需要java运行环境:是一个纯 Java 库用来管理.查看和处理Git 资料库.即一个基于Java的分布式版本控制系统. 1 ...
- kafka的分区
分区会均匀的分配到不同的broke上,即不同的机器上.
- kafka-manager 创建 topic【转】
1,add cluster 添加cluster 添加cluster 选择一下kafka的版本 2,创建topic 添加topic 3,查看topic 查看topic
- Spring学习指南-第二章-Spring框架基础(完)
第二章 Spring框架基础 面向接口编程的设计方法 在上一章中,我们看到了一个依赖于其他类的POJO类包含了对其依赖项的具体类的引用.例如,FixedDepositController 类包含 ...
- c,使用lib,dll
lib使用: #include "xxx.h" // lib的头文件 #pragma comment(lib, "xxx.lib") 这样会将lib里的数据编译 ...
- 使用 kill 命令杀死 java进程,你用对了吗?
在本地调试agent相关功能,需要经常性的杀掉Java进程,验证一些极端情况. 每次都是本能执行如下步骤 jps kill -9 <pid> reboot 有一次验证,发现代码中添加的Sh ...
- Quartz时间配置(周期任务)
序号 说明 是否必填 允许填写的值 允许的通配符 1 秒 是 0-59 , - * / 2 分 是 0-59 , - * / 3 小时 是 0-23 , - ...