Go基础

1. Golang 中常用的并发模型

  • 通过channel通知实现并发控制
  • 通过sync包中的WaitGroup实现并发控制

在WaitGroup里主要有三个方法:

  • Add, 可以添加或减少 goroutine的数量.
  • Done, 相当于Add(-1).
  • Wait, 执行后会堵塞主线程,直到WaitGroup 里的值减至0。
func main(){
    var wg sync.WaitGroup
    var urls = []string{
        "http://www.golang.org/",
        "http://www.google.com/",
    }
    for _, url := range urls {
        wg.Add(1)
        go func(url string) {
            defer wg.Done()
            http.Get(url)
        }(url)
    }
    wg.Wait()
}

 

 

注:在 WaitGroup 第一次使用后,不能被拷贝

应用示例:

func main(){
 wg := sync.WaitGroup{}
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(wg sync.WaitGroup, i int) {
            fmt.Printf("i:%d", i)
            wg.Done()
        }(wg, i)
    }
    wg.Wait()
    fmt.Println("exit")
}

 

 

上面运行会提示所有的 goroutine 都已经睡眠了,出现了死锁。这是因为 wg 给拷贝传递到了 goroutine 中,导致只有 Add 操作,其实 Done操作是在 wg 的副本执行的。因此 Wait 就死锁了。

  • 在Go 1.7 以后引进的强大的Context上下文,实现并发控制

通常,在一些简单场景下使用 channel 和 WaitGroup 已经足够了,但是当面临一些复杂多变的网络并发场景下 channel 和 WaitGroup 显得有些力不从心了。 比如一个网络请求 Request,每个 Request 都需要开启一个 goroutine 做一些事情,这些 goroutine 又可能会开启其他的 goroutine,比如数据库和RPC服务。 所以我们需要一种可以跟踪 goroutine 的方案,才可以达到控制他们的目的,这就是Go语言为我们提供的 Context,称之为上下文非常贴切,它就是goroutine 的上下文。

2. nil slice和空slice

var slice []int     是一个nil slice,直接用会越界

slice := []int{}    是一个空slice

3. 进程、线程和协程

  • 进程

进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。

  • 线程

线程是进程的一个实体,是CPU调度和分派的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈)。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。

  • 协程

协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

线程和协程的区别:

  • 线程的切换由操作系统负责调度,协程由用户自己进行调度,因此减少了上下文切换,提高了效率。
  • 线程的默认Stack大小是1M,而协程更轻量,接近1K。因此可以在相同的内存中开启更多的协程。
  • 由于在同一个线程上,因此可以避免竞争关系而使用锁。
  • 适用于被阻塞的,且需要大量并发的场景。但不适用于大量计算的多线程,遇到此种情况,更好实用线程去解决。

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

Channel是Go中的一个核心类型,可以把它看成一个管道,Channel也可以理解是一个先进先出的队列,通过管道进行通信。

Golang的Channel,发送一个数据到Channel 和 从Channel接收一个数据 都是 原子性的。而且Go的设计思想就是:不要通过共享内存来通信,而是通过通信来共享内存,前者就是传统的加锁,后者就是Channel。也就是说,设计Channel的主要目的就是在多任务间传递数据的,这当然是安全的。

解决数据竞争(Data race),也可以使用管道解决,使用管道的效率要比互斥锁Mutex高。

Go垃圾回收机制

1. 当前Golang使用的垃圾回收机制是三色标记法配合写屏障辅助GC,三色标记法是标记-清除法的一种增强版本。

2. 从root根出发扫描所有根对象,将他们引用的对象标记为灰色,将灰色对象置为黑色,将灰色对象引用的对象再置为灰色;以此循环,知道灰色对象队列为空。此时白色对象即为垃圾。

注:root区域主要是程序运行到当前时刻的栈和全局数据区域。

3. GC优化思路

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

a.避免string与[]byte转化;

两者发生转换的时候,底层数据结结构会进行复制,因此导致 gc 效率会变低。

b.对于string的连接操作,少量小文本拼接,用 “+” ;大量小文本拼接,用 strings.Join;大量大文本拼接,用 bytes.Buffer。

 

4. GC触发条件

a. 超过内存大小阈值

b. 达到定时时间,阈值是由一个gcpercent的变量控制的,当新分配的内存占已在使用中的内存的比例超过gcprecent时就会触发。

c. 调用runtime.GC()时,主动触发,如果GC已经启动则跳过。

比如一次回收完毕后,内存的使用量为5M,那么下次回收的时机则是内存分配达到10M的时候。也就是说,并不是内存分配越多,垃圾回收频率越高。 如果一直达不到内存大小的阈值呢?这个时候GC就会被定时时间触发,比如一直达不到10M,那就定时(默认2min触发一次)触发一次GC保证资源的回收。

原文:https://juejin.im/post/6844903917650722829

Mysql join操作

1.left join 会以左表为全量数据,然后on匹配右表的,所以left join里面对左表的on条件无效。right join同理。

2.left join和right join的on和where区别:on作为连接的条件(左连接时,on左表的条件无效,on右表的条件使得右表对应不满足条件的行为NULL,行数和没有条件还是一样),on先生成临时表,where再在临时表中筛选。

3.左连接时,尽量选择数据量小的表作为左表,可以减少筛选次数。右连接同理。

4.inner join的on和where作用相同,但是inner join只是自己判断选择哪个表作为左表,临时表的数据量不会变小。

 

5.在使用left join时,on和where条件的区别如下:

a. on条件是在生成临时表时使用的条件,它不管on中的条件是否为真,都会返回左边表中的记录。

b.where条件是在临时表生成好后,再对临时表进行过滤的条件。这时已经没有left join的含义(必须返回左边表的记录)了,条件不为真的就全部过滤掉。

CPU缓存一致性协议(MESI Protocol)

  • 通过 MESI protocol,任何一个 CPU 要写入资料前,首先会给其它CPU发送Invalid消息,在其它 CPU 回 invalidate ack后, 才能将资料到自己的 cache,并在稍后将资料写回 memory。但是因为其他CPU可能回ack慢,所以该CPU会不等ack,先写入store buffer,然后继续做事,之后收到ack再更新cache的状态。所以CPU 读资料的顺序: store buffer → cache → memory。
  • 其他CPU 收到invalid消息后会先记录到invalidate queue里然后立即回 invalidate ack,稍后再处理 invalidate。

原文:https://medium.com/fcamels-notes/%E5%BE%9E%E7%A1%AC%E9%AB%94%E8%A7%80%E9%BB%9E%E4%BA%86%E8%A7%A3-memry-barrier-%E7%9A%84%E5%AF%A6%E4%BD%9C%E5%92%8C%E6%95%88%E6%9E%9C-416ff0a64fc1

Golang细节

1. 复数

package main

import "fmt"

func main(){
	a:=complex(1,2)
	b:=complex(3,4)   // 等于 b:= 3 + 4i
	fmt.Println(a*b)
	fmt.Println(real(a*b))
	fmt.Println(imag(a*b))
}

 

结果:

2.