golang 辨析

https://www.golangroadmap.com/interview/books/questions/golang/4.html

Golang中除了加Mutex锁以外还有哪些方式安全读写共享变量

sync.Mutex
sync.RWMutex
sync.WaitGroup
channel

无缓冲Chan的发送和接收是否同步

Golang并发机制以及它所使用的CSP并发模型

CSP即通信顺序进程(communicating sequential processes),该模型也是由独立的、并发执行的实体所组成,实体之间通过发送消息进行通信。go 中的 csp 模型 channel 对于goroutine来说是匿名的,不需要和 gid 绑定,通过 channel 完成 goroutine 之间的通信。

Golang中常用的并发模型

https://blog.csdn.net/weixin_39658118/article/details/104531431

  • Barrier模式(阻塞直到聚合所有 goroutine 返回结果)
  • Promise模式
  • Pipeline模式
  • WorkersPool模式
  • Pub/Sub模式
  • channel通知实现并发控制
  • sync包中的WaitGroup实现并发控制
  • context
早期版本(好像是1.1之前),是GM模型,全局维护一个G队列,M从全局队列中获取G来执行,全局数据必然会有一个全局大锁来保证队列安全,效率提不上来;
目前版本,GMP模型,M不再需要直接从全局队列里面获取G,而是从P维护的本地队列中获取G来执行,P负责从别的P或者全局获取G,
而M必须跟一个P绑定才能获取到G来执行,同时也进行了很多细节上的优化,比如M遇到syscall时,一定时间之后会跟当前P解绑等等。

Go中对nil的Slice和空Slice的处理是一致的吗

var nilSlice []string
fmt.Println(nilSlice == nil)              // Output: true
emptySlice := make([]string, 5)
fmt.Println(emptySlice == nil)            // Output: false

协程和线程和进程的区别

进程是操作系统分配资源的基本单位,一个应用程序在一个进程中运行;
线程是CPU调度执行的基本单位,线程存在于进程之中,是实际执行代码的单位;
协程存在于线程之中,共享本地变量,调度速度快;

Golang的内存模型中为什么小对象多了会造成GC压力

通常小对象过多会导致GC三色法消耗过多的GPU。优化思路是,减少对象分配。

Go中数据竞争问题怎么解决?

使用channel sync.Mutex sync.WaitGroup等
使用 go run -race 检测

什么是channel,为什么它可以做到线程安全?

channel是Go语言并发机制的重要组成部分,通过channel不同的goroutine可以进行通讯来共享数据,分为buffer channel 和 unbuffer channel,一个是异步写入接受,一个是同步操作,使用前需要使用make方法来申请内存;
channel的底层实现中,hchan结构体中采用Mutex锁来保证数据读写安全。对于不同goroutine,这个可以看作是一个全局的锁,不管多少goroutine写入或者读取都需要获取锁才能操作channel数据。

Golang垃圾回收算法?

栈扫描(开始时STW),所有对象最开始都是白色.
从 root开始找到所有可达对象(所有可以找到的对象),标记为灰色,放入待处理队列。
遍历灰色对象队列,将其引用对象标记为灰色放入待处理队列,自身标记为黑色。
清除(并发) 循环步骤3直到灰色队列为空为止,此时所有引用对象都被标记为黑色,所有不可达的对象依然为白色,白色的就是需要进行回收的对象。

GC的触发条件

主动触发(手动触发),通过调用runtime.GC 来触发GC,此调用阻塞式地等待当前GC运行完毕.
被动触发,分为两种方式:
a. 使用系统监控,当超过两分钟没有产生任何GC时,强制触发 GC.
b. 使用步调(Pacing)算法,其核心思想是控制内存增长的比例,当前内存分配达到一定比例则触发.

Go的GPM如何调度?

P作为调度者,M作为执行者,G作为需要执行的任务。
M不会自行去找能执行的任务,每个M都需要跟一个P绑定,P维护一个本地的G队列,M优先执行P本地队列中的G.
如果本地队列没有G,则P会从全局队列中或者其他P的本地队列中获取G;
G被创建优先放置在与该M绑定的P的本地队列中,如果本地队列已满,则P会将前半部分与新创建的G送到全局队列中维护,等待其他P从全局队列中获取。

并发编程概念是什么?

从微观上并行是同时运行,这只能通过多核技术
并发是从宏观上,多个任务同时进行,但是微观上可能是同时执行的也可能不是

Go语言的栈空间管理是怎么样的?

怎么查看Goroutine的数量?

runtime.NumGoroutine()

Go中的锁有哪些?

sync.Mutex
sync.RWMutex
sync.Map

Go的Struct能不能比较?

不同类型的struct无法比较
相同类型的struct,如果属性都是可比类型则可以比较,如果存在不可比类型则不可比
map slice都是不可比较类型

Go的select可以用于什么?

Golang 的 select 机制可以理解为是在语言层面实现了和 select, poll, epoll 相似的功能:监听多个描述符的读/写等事件,一旦某个描述符就绪(一般是读或者写事件发生了),就能够将发生的事件通知给关心的应用程序去处理该事件。 golang 的 select 机制是,监听多个channel,每一个 case 是一个事件,可以是读事件也可以是写事件,随机选择一个执行,可以设置default,它的作用是:当监听的多个事件都阻塞住会执行default的逻辑

Context包的用途是什么?

多用于传递上下文,用于关键数据传递以及超时控制。

Go主协程如何等其余协程完再操作?

var wg sync.WaitGroup
wg.Add(2)
wg.Done()
wg.Wait()

Go中的map如何实现顺序读取?

Go中map如果要实现顺序读取的话,可以先把map中的key,通过sort包排序.
通过sort中的排序包进行对map中的key进行排序.

Go中CAS是怎么回事?

CAS算法(Compare And Swap),是原子操作的一种, CAS算法是一种有名的无锁算法。
Go中的CAS操作是借用了CPU提供的原子性指令来实现。CAS操作修改共享变量时候不需要对共享变量加锁,而是通过类似乐观锁的方式进行检查,本质还是不断的占用CPU 资源换取加锁带来的开销
"sync/atomic"包中的atomic.CompareAndSwapInt32

Go值接收者和指针接收者的区别?

type Person struct {
    age int
}

// 值接收者
func (p Person) Elegance() int {
    return p.age
}

// 指针接收者
func (p *Person) GetAge() {
    p.age += 1
}
  1. 值接收者,修改的都是对象的副本;指针接收者,修改对象本身
  2. 特殊情况,如果类型本身就是slice map channel innterface这种引用类型,那么即使使用值接收者,也会改变对象的值

Go中的defer函数使用下面的两种情况下结果是什么?

func testDefer() {
    a := 1
    defer fmt.Println("the value of a1:", a) //1
    a++

    defer func() {
        fmt.Println("the value of a2:", a) //4
    }()
    a++

    defer func() {
        fmt.Println("the value of a3:", a) //4
    }()
    a++
}

the value of a3: 4 // 注意这个顺序是后加的先执行,defer执行的形参参数是定义是就算好了,而使用更全局的变量则不是
the value of a2: 4
the value of a1: 1

在Go函数中为什么会发生内存泄露?

  1. 当有一个全局对象时,可能不经意间将某个变量附着在其上,且忽略的将其进行释放,则该内存永远不会得到释放。
  2. 如果一个程序持续不断地产生新的 goroutine、且不结束已经创建的,就会造成内存泄漏的现象.目前的golang无法自动释放,如下

    for i := 0; i < 10000000; i++ {
     go func() {
         select {} // 此处的select将一直阻塞,而让所有协程无法退出
     }()
    }

Go的值类型和引用类型

值类型:int,float,bool,string,struct和array.
变量直接存储值,分配栈区的内存空间,这些变量所占据的空间在函数被调用完后会自动释放。

引用类型:slice,map,chan和值类型对应的指针.
变量存储的是一个地址(或者理解为指针),指针指向内存中真正存储数据的首地址。内存通常在堆上分配,通过GC回收。

Go中new和make的区别?

make 只能用来分配及初始化类型为 slice、map、chan 的数据。new 可以分配任意类型的数据;
new 分配返回的是指针,即类型 *Type。make 返回引用,即 Type;
new 分配的空间被清零。make 分配空间后,会进行初始化

G0的作用?

对于一个线程来说,g0 总是它第一个创建的 goroutine。它会不断地寻找其他普通的 goroutine 来执行,直到进程退出。创建 goroutine、deferproc 函数里新建 _defer、垃圾回收相关的工作(例如 stw、扫描 goroutine 的执行栈、一些标识清扫的工作、栈增长)等等。

Go函数返回局部变量的指针是否安全?

在 Go 中是安全的,Go 编译器将会对每个局部变量进行逃逸分析。如果发现局部变量的作用域超出该函数,则不会将内存分配在栈上,而是分配在堆上

Go中两个Nil可能不相等吗?

Go中两个Nil可能不相等。
接口(interface) 是对非接口值(例如指针,struct等)的封装,内部实现包含 2 个字段,类型 T 和 值 V。一个接口等于 nil,当且仅当 T 和 V 处于 unset 状态(T=nil,V is unset)。
接口值与非接口值比较时,会先将非接口值尝试转换为接口值,再比较。

Go中的逃逸分析是什么?

如果变量的作用域不会扩大并且其行为或者大小能够在编译的时候确定,一般情况下都是分配到栈上,否则就可能发生内存逃逸分配到堆上。

  1. 局部变量其实存在栈内存stack中,但是如果某种情况导致这个局部变量的作用域扩大了,他就可能分配到堆heap上
  2. 编译时候确定的内存大小有误,会导致内存分配到heap,例如:slice
  3. slice只要返回我发现就不会分派在heap
  • 发送指针的指针或值包含了指针到channel 中,由于在编译阶段无法确定其作用域与传递的路径,所以一般都会逃逸到堆上分配。
  • slices 中的值是指针的指针或包含指针字段。一个例子是类似[]*string 的类型。这总是导致 slice 的逃逸。即使切片的底层存储数组仍可能位于堆栈上,数据的引用也会转移到堆中。
  • slice 由于 append 操作超出其容量,因此会导致 slice 重新分配。这种情况下,由于在编译时 slice 的初始大小的已知情况下,将会在栈上分配。如果 slice 的底层存储必须基于仅在运行时数据进行扩展,则它将分配在堆上。
  • 调用接口类型的方法。接口类型的方法调用是动态调度,实际使用的具体实现只能在运行时确定。考虑一个接口类型为 io.Reader 的变量 r。对 r.Read(b) 的调用将导致 r 的值和字节片b的后续转义并因此分配到堆上。
  • 尽管能够符合分配到栈的场景,但是其大小不能够在在编译时候确定的情况,也会分配到堆上.
    逃逸分析如下

    go build -gcflags "-m" test.go

Goroutine发生了泄漏如何检测?

  1. 全局对象
  2. Goroutine 泄漏,长期不会退出的协程,这个东西不会自动退出
    Gops去检测诊断当前在系统上运行的Go进程的占用的资源

Go函数返回局部变量的指针是否安全?

在 Go 中是安全的,Go 编译器将会对每个局部变量进行逃逸分析。如果发现局部变量的作用域超出该函数,则不会将内存分配在栈上,而是分配在堆上

Go中两个Nil可能不相等吗?

接口(interface) 是对非接口值(例如指针,struct等)的封装,内部实现包含 2 个字段,类型 T 和 值 V。一个接口等于 nil,当且仅当 T 和 V 处于 unset 状态(T=nil,V is unset)。

func main() {
    var p *int = nil
    var i interface{} = p
    fmt.Println(i == p) // true
    fmt.Println(p == nil) // true
    fmt.Println(i == nil) // false
}

i 与nil比较时,会将nil转换为接口(T=nil, V=nil),与i(T=*int, V=nil)不相等,因此 i != nil。因此 V 为 nil ,但 T 不为 nil 的接口不等于 nil。

此处评论已关闭