跌跌撞撞一年时间,终于开始写第三期了 ,其实这一期的内容早就有了,只不过一直没有放到博客上。不能停下学习的脚步,要继续前进呀!
1、JSON unmarshal map的结果是不确定的
测试代码:
package main
import (
"encoding/json"
"log"
)
const str = `{"a":"aaaa","b":"baaa","c":100}`
func main() {
for i := 0; i < 10; i++ {
var m map[string]interface{}
if err := json.Unmarshal([]byte(str), &m); err != nil {
log.Fatal(err)
}
for k, v := range m {
log.Printf("k: %s, v: %v", k, v)
}
log.Println("--------------------------------------")
}
}
测试结果:
2、火焰图
火焰图(flame graph)是性能分析的利器,样子如下图所示
火焰图是基于perf结果产生的SVG图片,用来展示CPU的调用栈
y轴表示调用栈,每一层都是一个函数。调用栈越深,火焰就越高,顶部就是正在执行的函数,下方就是它的父函数。
x轴表示抽样数,如果一个函数在x轴占据的宽度越宽,就表示它被抽到的次数多,即执行的时间长。
注意
:x轴不代表时间,不是所有的调用栈合并后,按字母顺序排列的。
火焰图就是看顶层的哪个函数占据的宽度最大。只要有“平顶”,就表示该函数可能存在性能问题。颜色没有特殊含义,因为火焰图表示的是CPU的繁忙程度,所以一般选择暖色调。
参考:阮一峰-如何读懂火焰图:
http://www.ruanyifeng.com/blog/2017/09/flame-graph.html
3、REST和SOAP
web服务技术有SOAP(Simple Object Access Protocol,简单对象访问协议)和REST(Representational State Transfer,表示性状态转移)两种方案。
SOAP是基于XML的简易协议,可使应用程序在HTTP之上进行信息交换。SOAP是用于访问网络服务的协议。
4、golang程序如何“优雅的退出”
goroutine作为golang并发的核心,不仅需要关注它们的创建和管理,也需要关注如何合理地退出这些协程。不合理退出可能会造成阻塞、panic、程序行为异常、数据结果不正确等问题。goroutine在退出方面,不像线程和进程,不能通过某种手段强制关闭它们,只能等待goroutine主动退出。下面介绍3种优雅退出goroutine的方法
(1)使用 for-range退出
for-range是使用频率很高的结构,常用来遍历数据,range能够感知channel的关闭,当channel被发送数据的协程关闭时,range就会结束,接着退出for循环。它在并发中的使用场景是:当协程只从1个channel读取数据,然后进行处理,处理后协程退出。下面举个例子,当in通道被关闭时,协程可自动退出。
go func(in <-chan int){
//Using for-range to exit goroutine
//range has the ability to detect the close/end of a channel
for x := range in {
fmt.Printf("Process %d\n", x)
}
}(inCh)
(2)使用ok退出
for提供了多路复用的能力,所以for-select可以让函数具有持续多路处理多个channel的能力。但select没有感知channel的关闭,会引出2个问题:
1、继续在关闭的通道上读,会读到通道传输数据类型的零值,如果是指针类型,读到nil,继续处理还会产生nil
2、继续在关闭的通道上写,将会panic
处理方案:
第一种,如果某个通道关闭后,需要退出协程,直接return即可
go func() {
for{
select{
case x, ok := <-in:
if !ok{
return
}
fmt.Printf("Process %d\n", x)
processedCnt++
case <-t.C:
fmt.Printf("Working, processedCnt = %d\n", processedCnt)
}
}
}()
第二种,如果某个通道关闭了,不再处理该通道,而是继续处理其他case,退出是等待所有的可读通道关闭。我们需要使用select的一个特征,select不会在nil的通道上进行等待。这种情况,把只读通道设置为nil即可解决
go func() {
for{
select{
case x, ok := <-in1:
if !ok{
in1=nil
}
case y, ok := <-in2:
if !ok{
in2=nil
}
case <-t.C:
fmt.Printf("Working, processedCnt = %d\n", processedCnt)
}
if in1 == nil && in2 == nil{
return
}
}
}()
(3)使用退出通道退出
func worker(stopCh <- chan struct{}){
go func() {
defer fmt.Println("worker exit")
for{
select{
case <- stopCh:
fmt.Println("Recv stop signal")
return
case <- t.C:
fmt.Println("Working .")
}
}
}()
return
}
5、golang的三色标记算法
Go语言能够支持实时的,高并发的消息系统,在高达百万级别的消息系统中能够将延迟降低到100ms以下,很大一部分需要归功于Go高效的垃圾回收系统。
原理:
1、起初所有对象都是白色
2、从根出发扫描所有可达对象,标记为灰色,放入待处理队列
3、从队列取出灰色对象,将其引用对象标记为灰色放入队列,自身标记为黑色
4、重复3,直到灰色对象队列为空。此时白色对象即为垃圾,进行回收。
6、nil slice与empty slice的区别?
nil map与empty map的区别?
var slice []int
slice[1] = 0
会报数组越界的错误,因为只是声明了slice,没有给实例化的对象。如果是cpp的vector,就可以直接使用了,golang不行。
此时的slice的值是nil,这种情况下可以用于需要返回slice的函数,当函数出现异常的时候,保证函数依然会有nil的返回值。empty slice是指slice不为nil,但是slice没有值,slice的底层的空间是空的。此时的定义如下:
slice := make([]int, 0)
或者
slice := []int{}
当我们查询或者处理一个空的列表的时候,非常有用,它会告诉我们返回的是一个列表,但是列表内没有任何值。
参考文档:
深度解析Go语言中「切片」的三种特殊状态:
https://juejin.im/post/5bea58df6fb9a049f153bca8
nil slice可以用append吗?
测试代码:
var a []int
b := 2
a = append(a, b)
fmt.Printf("%v", a)
打印
[2]
可以append
但是map不行
测试代码:
var a map[string]string
a["a"] = "b"
fmt.Printf("%v", a)
会报错