熟悉golang关键点总结

1.gc垃圾回收算法:标记-清除法

基本原理:从根(包括全局指针以及goroutine栈上指针)出发,标记可达节点为灰色,然后是继续标记灰色节点可达的节点为灰色,同时自身由灰色变为黑色,不断重复,剩下所有的白色节点为垃圾回收节点

2.减轻gc负担的根本方法是:减少对象数量,特别是减少小对象的大量创建
3.goroutine调度原理:半抢占式(在函数入口处插入协作代码)的协作调度,可以理解为公平协作调度和抢占式调度的结合体

GPM模型,一个M对应一个P对应一个多个G的队列,外加一个G的全局队列
G被调度的时机有以下几种场景:

a. 简单抢占式调度:一旦某个G中出现死循环或永久循环的代码逻辑,那么G将永久占用分配给它的P和M,位于同一个P中的其他G将得不到调度,出现“饿死”的情况,在Go 1.2中实现了抢占式调度来解决这个问题,原理则是在每个函数或方法的入口,加上一段额外的代码,让runtime有机会检查是否需要执行抢占调度(是否需要调度的判断标准是由独立的sysmon线程来维护的)。这种解决方案只能说局部解决了“饿死”问题,对于没有函数调用,纯算法循环计算的G,scheduler依然无法抢占。

b. channel阻塞、锁阻塞( atomic, mutex, 或者 channel)等阻塞场景:如果G被阻塞在某个channel操作或者G被阻塞到同步互斥操作,也就是 Lock(),Unlock() 等锁的情况下,G会被放置到某个wait队列中,原来的M不会阻塞,会尝试运行下一个runnable的G,当被放在wait队列中的G不再阻塞时,可以将G重新放到队列中等待调度执行

c. 异步system call 例如network I/O情况下的调度:当G进行network I/O操作时,G会被放置到某个wait队列中,而M会尝试运行下一个runnable的G,当被放在wait队列中的G不再阻塞时,可以将G重新放到队列中等待调度执行

c. 同步system call例如文件io操作阻塞情况下的调度:如果G被阻塞在某个system call操作上,那么不光G会阻塞,执行该G的M也会解绑P(实质是被sysmon抢走了),与G一起进入sleep状态。如果此时有idle的M,则P与其绑定继续执行其他G;如果没有idle M,但仍然有其他G要去执行,那么就会创建一个新M。当阻塞在syscall上的G完成syscall调用后,G会去尝试获取一个可用的P,如果没有可用的P,那么G会被标记为runnable,之前的那个sleep的M将再次进入sleep。Go语言完全是自己封装的系统调用,所以在封装系统调用的时候,可以做不少手脚,也就是进入系统调用的时候执行entersyscall,退出后又执行exitsyscall函数。 也只有封装了entersyscall的系统调用才有可能触发重新调度。

4.slice与数组:slice是引用类型,数组是值类型,slice可以动态扩容,减少动态内存拷贝可以预先分配
5.channel原理:golang参考CSP模型实现的类似管道的结构,本质是一个循环队列+锁+两个Goroutine等待队列,channel是不同goroutine间通信的消息通道

channel引起的panic场景:

a. 关闭一个未初始化(nil) 的 channel ;
b. 重复关闭同一个 channel;
c. 向一个已关闭的 channel 中发送消息;

从一个已关闭的 channel 中读取消息永远不会阻塞,并且会返回一个为 false 的 ok标记,可以用它来判断 channel 是否关闭。
单向 channel 一般是在声明时会用到,防止传递进函数的channel被随意读取获取写入,限制操作,比如

func foo(ch chan<- int) <-chan int {...}

chan<- int 表示一个只可写入的 channel,<-chan int 表示一个只可读取的 channel。上面这个函数约定了 foo 内只能从向 ch 中写入数据,返回只一个只能读取的 channel,这样在方法声明时约定可以防止 channel 被滥用,这种约定在编译期间就确定下来了。
channel的实现原理:channel内部主要组成:一个环形数组实现的队列,用于存储消息元素;两个链表实现的 goroutine 等待队列,用于存储阻塞在 recv 和 send 操作上的 goroutine;一个互斥锁,用于各个属性变动的同步。

6.golang内存泄漏场景:

a. 永远处于阻塞状态的goroutine,这将导致这些 goroutine 中使用的许多代码块永远无法进行垃圾收集,如下场景案例:
如果将以下函数作为 goroutine 的启动函数并将一个 nil channel 参数传递给它, 则 goroutine 将永远阻塞. Go 运行时认为 goroutine 仍然存活, 所以为 s 分配的内存块将永远不会被收集.

func k(c <-chan bool) {
    s := make([]int64, 1e6)
    if <-c { // 如果 c 为 nil, 这里将永远阻塞
        _ = s
        // 使用 s, ...
    }
}

b.终结器(Finalizers):为循环引用组内的成员设置 finalizer 可能会阻止为这个循环引用组分配的所有内存块被收集。
在下列函数被调用并退出之后, 为 x和 y 分配的内存块不保证在未来会被垃圾收集器回收.

func memoryLeaking() {
    type T struct {
        v [1<<20]int
        t *T
    }

    var finalizer = func(t *T) {
         fmt.Println("finalizer called")
    }
    
    var x, y T
    
    // SetFinalizer 会使 x 逃逸到堆上.
    runtime.SetFinalizer(&x, finalizer)
    
    // 以下语句将导致 x 和 y 变得无法收集.
    x.t, y.t = &y, &x // y 也逃逸到了 堆上.
}
7.interface与nil
go 中的接口分为两种 
var Xxxx interface{}
type Xxxx  interface {
    Show()
}
底层结构:
type eface struct {      //空接口
    _type *_type         //类型信息
    data  unsafe.Pointer //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
}
type iface struct {      //带有方法的接口
    tab  *itab           //存储type信息还有结构实现方法的集合
    data unsafe.Pointer  //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
}
data指向了nil 并不代表interface 是nil
8.golang内存分配原理

new与make最终调用的都是mallocgc函数来进行分配内存,在启动时向操作系统预分配大块内存,内存分配以TCMalloc的结构进行设计, 不同大小的对象以不同class的span进行分配,减少内存碎片, 每个P都有cache减少锁冲突,同时还有zero分配采用同一个全局对象, tiny分配器加快小对象分配的优化。

9.切片会导致整个底层数组被锁定

切片会导致整个底层数组被锁定,底层数组无法释放内存。如果底层数组较大会对内存产生很大的压力。

func main() {
    headerMap := make(map[string][]byte)

    for i := 0; i < 5; i++ {
        name := "/path/to/file"
        data, err := ioutil.ReadFile(name)
        if err != nil {
            log.Fatal(err)
        }
        headerMap[name] = data[:1]
    }

    // do some thing
}

解决的方法是将结果克隆一份,这样可以释放底层的数组:

func main() {
    headerMap := make(map[string][]byte)

    for i := 0; i < 5; i++ {
        name := "/path/to/file"
        data, err := ioutil.ReadFile(name)
        if err != nil {
            log.Fatal(err)
        }
        headerMap[name] = append([]byte{}, data[:1]...)
    }

    // do some thing
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,932评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,554评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,894评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,442评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,347评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,899评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,325评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,980评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,196评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,163评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,085评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,826评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,389评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,501评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,753评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,171评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,616评论 2 339

推荐阅读更多精彩内容