go

2022-02-20  本文已影响0人  Supreme_DJK

[TOC]

map

map 的底层实现原理是什么 - 码农桃花源 (gitbook.io)

底层数据结构

// A header for a Go map.
type hmap struct {
    // 元素个数,调用 len(map) 时,直接返回此值
    count     int
    flags     uint8
    // buckets 的对数 log_2
    B         uint8
    // overflow 的 bucket 近似数
    noverflow uint16
    // 计算 key 的哈希的时候会传入哈希函数
    hash0     uint32
    // 指向 buckets 数组,大小为 2^B
    // 如果元素个数为0,就为 nil
    buckets    unsafe.Pointer
    // 扩容的时候,buckets 长度会是 oldbuckets 的两倍
    oldbuckets unsafe.Pointer
    // 指示扩容进度,小于此地址的 buckets 迁移完成
    nevacuate  uintptr
    extra *mapextra // optional fields
}

map扩容

为避免大量key落在一个桶中,退化成链表,导致查询效率变为O(n),装载因子被提出,用来衡量该情况。

loadFactor := count / (2^B)

count : map中元素个数

B:2^B表示buckets数目

context

context作用

  1. 在 一组 goroutine 之间传递共享的值
  2. 取消goroutine
  3. 防止goroutine泄露
  1. 不要将 Context 塞到结构体里。直接将 Context 类型作为函数的第一参数,而且一般都命名为 ctx。
  2. 不要向函数传入一个 nil 的 context,如果你实在不知道传什么,标准库给你准备好了一个 context:todo。
  3. 不要把本应该作为函数参数的类型塞到 context 中,context 存储的应该是一些共同的数据。例如:登陆的 session、cookie 等。
  4. 同一个 context 可能会被传递到多个 goroutine,别担心,context 是并发安全的。

context.Value

type valueCtx struct {
    Context
    key, val interface{}
}
  1. key要求是可比较的

  2. 属于一个树结构

    <img src="C:\Users\Supreme\AppData\Roaming\Typora\typora-user-images\image-20220220230609169.png" alt="image-20220220230609169" style="zoom:50%;" />

  3. 取值过程,是会向上查找的

  4. 允许存在相同的key,向上查找会找到最后一个key相等的节点,即层数高的节点

Goroutine

  1. M必须拥有P才可执行G中的代码,P含有一个包含多个G的队列,P可以调度G交由M执行
  2. 所有可执行go routine都放在队列中:
    • 全局队列(GRQ):存储全局可运行的goroutine(从系统调用中恢复的G);
    • 本地可运行队列(LRQ):存储本地(分配到P的)可运行的goroutine
  3. workingschedule:各个P中维护的G队列很可能是不均衡的;空闲的P会查询全局队列,若全局队列也空,则会从其他P中窃取G(一般每次取一半)。

goroutine和线程的区别

  1. 内存占用:goroutine默认栈为2KB,线程至少需要1MB
  2. 创建和销毁:goroutine由go runtime管理,属于用户级别的,消耗小;线程是操作系统创建的,是内核级别的,消耗巨大
  3. 切换:goroutine切换只需要保存三个寄存器,线程切换需要寄存器

M:N模型(M个线程,N个goroutine)

  1. go runtime负责管理goroutine,Runtime会在程序启动的时候,创建M个线程(CPU执行调度的单位),之后创建的N个goroutine都会依附在这M个线程上执行。
  2. 同一时刻,一个线程只能跑一个goroutine,当goroutine发生阻塞时,runtime会把它调度走,让其他goroutine来执行,不让线程闲着

系统调用

同步调用

G1即将进入系统调用时,M1将释放P,让某个空闲的M2获取P并继续执行P队列中剩余的G(即M2接替M1的工作);M2可能来源于M的缓存池,也可能是新建的。当G1系统调用完成后,根据M1能否获取到P,将对G1做不同的处理:

异步调用

  1. M 不会被阻塞,G 的异步请求会被“代理人” network poller 接手,G 也会被绑定到 network poller
  2. 等到系统调用结束,G 才会重新回到 P 上。
  3. M 由于没被阻塞,它因此可以继续执行 LRQ 里的其他 G。
上一篇 下一篇

猜你喜欢

热点阅读