开发系列go-2
1.go 异常和错误(Error 接口)
在Go语言中,错误和异常是不相同的俩个概念,错误被认为是一种可以预期的结果;而异常则是一种非预期的结果, 发生异常可能表示程序中存在BUG或发生了其它不可控的问题。
Go语言推荐使 用 recover 函数将内部异常转为错误处理,这使得用户可以真正的关心业务相关的错误处理。
Recover()用法是:将Recover()写在defer中,并且在可能发生panic的地方之前,先调用此defer的东西(让系统方法域结束时,有代码要执行。)
当程序遇到panic的时候(当然,也可以正常的调用出现的异常情况),系统将跳过后面的代码,进入defer,如果defer函数中有recover(),则返回捕获到的panic的值。
func main() {
fmt.Println(divide(3,0))
}
func divide(num1 ,num2 int )int {
defer func() {
if err := recover(); err !=nil {
fmt.Printf("error: %s\n", err)
}
}()
return num1/num2
}
//manager *serverManager表示该 method 属于 NewManager类型对象中的方法
func (manager *serverManager) check(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
logrus.Error("catch panic error:{}", err)
}
}()
panic("我要抛出一个异常")
c.JSON(200, gin.H{
"message": "success",
})
return
}
Go语言没有异常系统,其使用 panic 触发宕机类似于其他语言的抛出异常,recover 的宕机恢复机制就对应其他语言中的 try/catch 机制。
2. 日志处理+日志切割
import (
"edge-node-red/server"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"github.com/sirupsen/logrus"
"time"
)
func init() {
path := "./app.log"
logrus.SetLevel(logrus.DebugLevel)
logrus.Info("==init==")
logrus.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: "2006-01-02 15:04:05",
})
/* 日志轮转相关函数
`WithLinkName` 为最新的日志建立软连接
`WithRotationTime` 设置日志分割的时间,隔多久分割一次
`WithMaxAge 和 WithRotationCount二者只能设置一个
`WithMaxAge` 设置文件清理前的最长保存时间
`WithRotationCount` 设置文件清理前最多保存的个数
*/
// 下面配置日志每隔 1 分钟轮转一个新文件,保留最近 3 天的日志文件,多余的自动清理掉。
// logfile, _ := os.OpenFile("./app.log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
writer, _ := rotatelogs.New(
path+".%Y%m%d%H%M",
rotatelogs.WithLinkName(path),
rotatelogs.WithMaxAge(time.Duration(72)*time.Hour),
rotatelogs.WithRotationTime(time.Duration(24)*time.Hour),
)
logrus.SetOutput(writer)
}
3.判断为空:
func GetURL() string {
url := config.Conf.NodeRed.Url
logrus.Info("global variable:url={}", url)
if len(url) == 0 || url == "" {
url = "http://127.0.0.1:1880"
}
return url
}
4.channel
4-1.无缓冲通道
func main() {
ch := make(chan int)
ch <- 10
fmt.Println("发送成功")
}
会报错:fatal error: all goroutines are asleep - deadlock!
func recv(c chan int) {
ret := <-c
fmt.Println("接收成功", ret)
}
func main() {
ch := make(chan int)
go recv(ch) // 启用goroutine从通道接收值
ch <- 10
fmt.Println("发送成功")
}
运行正常。结论:无缓冲通道也被称为同步通道
4-2. 通过协程和channel实现协程同步(子协程执行后主线程再执行)
func main() {
ch := make(chan struct{})
count := 2 // count 表示活动的协程个数
go func() {
fmt.Println("Goroutine 1")
ch <- struct{}{} // 协程结束,发出信号
}()
go func() {
fmt.Println("Goroutine 2")
ch <- struct{}{} // 协程结束,发出信号
}()
// 优雅的从通道循环取值
for range ch {
// 每次从ch中接收数据,表明一个活动的协程结束
count--
// 当所有活动的协程都结束时,关闭管道
if count == 0 {
close(ch)
}
}
fmt.Println("--main--")
}
4-3. 通过sync.WaitGroup实现协程同步(子协程执行后主线程再执行)
func main() {
var wg sync.WaitGroup
wg.Add(2) // 因为有两个动作,所以增加2个计数
go func() {
fmt.Println("Goroutine 1")
wg.Done() // 操作完成,减少一个计数
}()
go func() {
fmt.Println("Goroutine 2")
wg.Done() // 操作完成,减少一个计数
}()
wg.Wait() // 等待,直到计数为0
fmt.Println("--main--")
}
4-4. 多任务一个worker处理
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
//工作线程
func workerPool(jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Println("start job", j)
time.Sleep(time.Second)
fmt.Println("finish job", j)
results <- j
}
}
func init() {
wg = sync.WaitGroup{}
}
func main() {
const numJobs = 5
jobs := make(chan int) //
results := make(chan int) //
go workerPool(jobs, results)
go func() {
for r := range results {
fmt.Println("results :", r)
wg.Done() //接收到数据,表示完成了一份工作
}
}()
for i := 1; i <= numJobs; i++ {
wg.Add(1) //标记开始一份工作
jobs <- i
}
wg.Wait()
}
4-5. 使用select 切换协程
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
for i := 0; i < 10; i++ {
ch1 <- fmt.Sprintf("A%d", i)
}
}()
go func() {
for i := 0; i < 10; i++ {
ch2 <- fmt.Sprintf("B%d", i)
}
}()
go func() {
for {
select {
case v := <-ch1:
fmt.Println(v)
case v := <-ch2:
fmt.Println(v)
}
}
}()
time.Sleep(1e9)
}