1、go中make和new区别
new:
- 分配内存。内存里存的值是对应类型的零值。
- 只有一个参数。参数是分配的内存空间所存储的变量类型,Go语言里的任何类型都可以是new的参数,比如int, 数组,结构体,甚至函数类型都可以。
- 返回的是指针。
make:
- 分配和初始化内存。
- 只能用于slice, map和chan这3个类型,不能用于其它类型。如果是用于slice类型,make函数的第2个参数表示slice的长度,这个参数必须给值。
- 返回的是原始类型,也就是slice, map和chan,不是返回指向slice, map和chan的指针。
2、进程、线程、协程区别
进程:一个具有特定功能的程序运行在一个数据集上的一次动态过程。是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。(QQ、微信等等)
线程:一个进程可以有多个线程,每个线程会共享父进程的资源(创建线程开销占用比进程小很多,可创建的数量也会很多),有时被称为轻量级进程(Lightwight Process,LWP),是操作系统调度(CPU调度)执行的最小单位。
协程:一种比线程更加轻量级的存在,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。
3、go中channel底层是什么
什么是channel?
chan是Go中的一种特殊类型,不同的协程可以通过channel来进行数据交互。
channel分为有缓冲区与无缓冲区两种
channel底层?
channel是基于环形队列实现的。
type hchan struct { qcount uint // 队列中的总数据 dataqsiz uint // 循环队列的大小 buf unsafe.Pointer // 指向环形队列的元素数组 elemsize uint16 closed uint32 elemtype *_type // 元素类型 sendx uint // 发送指数 recvx uint // 收到指数 recvq waitq // 接收列表 sendq waitq // 发生列表 // lock protects all fields in hchan, as well as several // fields in sudogs blocked on this channel. // // Do not change another G's status while holding this lock // (in particular, do not ready a G), as this can deadlock // with stack shrinking. lock mutex }
图:未完待补充
4、go derfer执行顺序
1、多个defer的执行顺序为“后进先出”;
2、defer、return、返回值三者的执行逻辑应该是:return最先执行,return负责将结果写入返回值中;接着defer开始执行一些收尾工作;最后函数携带当前返回值退出。
我们知道defer后面一定要接一个函数的,所以defer的数据结构跟一般函数类似,也有栈地址、程序计数器、函数地址等等。
与函数不同的一点是它含有一个指针,可用于指向另一个defer,每个goroutine数据结构中实际上也有一个defer指针,该指针指向一个defer的单链表,每次声明一个defer时就将defer插入到单链表表头,每次执行defer时就从单链表表头取出一个defer执行。
5、go如何用两个协程交替打印出123456
协程1打印基数,协程2打印偶数,用一个channel实现
代码如下:
package main
import (
"fmt"
"sync"
)
//打印奇数
func PrintOddNumber(wg *sync.WaitGroup, ch chan int, num int) {
defer wg.Done()
for i := 0; i <= num; i++ {
ch <- i
if i%2 != 0 {
fmt.Println("奇数:", i)
}
}
}
//打印偶数
func PrintEvenNumber(wg *sync.WaitGroup, ch chan int, num int) {
defer wg.Done()
for i := 1; i <= num; i++ {
<-ch
if i%2 == 0 {
fmt.Println("偶数:", i)
}
}
}
func main() {
wg := sync.WaitGroup{}
ch := make(chan int)
wg.Add(1)
go PrintOddNumber(&wg, ch, 10)
go PrintEvenNumber(&wg, ch, 10)
wg.Wait()
}
结果:
奇数: 1
偶数: 2
奇数: 3
偶数: 4
奇数: 5
偶数: 6
奇数: 7
偶数: 8
奇数: 9
偶数: 10
6、go中slice和数组的区别
1. 数组定长,定义的时候就需要确定。切片长度不定,append时会自动扩容
2. 相同大小数组可以赋值,会拷贝全部内容。slice赋值和指针一样。数组和slice之间不能相互赋值。当然slice有自己的copy函数
3. 数组也可以进行切片,返回值是一个slice,改变slice时会同步修改数组内容,相当于取得了这个数组的指针
7、go中channel在你们项目中使用的场景举例
1、消息传递、消息过滤
2、信号广播
3、事件订阅与广播
4、请求、响应转发
5、任务分发
8、go中使用chan要注意什么
1、当需要不断从channel读取数据时,最好使用
for-range
读取channel,这样既安全又便利,当channel关闭时,for循环会自动退出,无需主动监测channel是否关闭,可以防止读取已经关闭的channel,造成读到数据为通道所存储的数据类型的零值。例如:for x := range ch { // }
2、读已关闭的channel会造成零值 ,如果不确定channel,需要使用
ok
进行检测。if v, ok := <-chan;ok{ /// }
3、s
elect
可以同时监控多个通道的情况,只处理未阻塞的case。当通道为nil时,对应的case永远为阻塞,无论读写。特殊关注:普通情况下,对nil的通道写操作是要panic的select { case <-ch: // default: // }
4、使代码更易读、更易维护,防止只读协程对通道进行写数据,但通道已关闭,造成panic。
5、有缓冲通道是异步的,无缓冲通道是同步的,有缓冲通道可供多个协程同时处理,在一定程度可提高并发性。
6、使用
select
和time.After
,看操作和定时器哪个先返回,处理先完成的,就达到了超时控制的效果func WorkWithOutTime(t time.Duration) (int, error) { select { case ret := <-Work(): return ret, nil case <-time.After(t): return 0, errors.New("timeout") } } func Work() <-chan int { ch := make(chan int) go func() { // }() return ch }
7、是为操作加上超时的扩展,这里的操作是channel的读或写
func OnlyRead(ch chan int) { select { case <-ch: // default: // } }
8、channel本质上传递的是数据的拷贝,拷贝的数据越小传输效率越高,传递结构体指针,比传递结构体更高效
9、说一下go中的CSP
CSP(communicating sequential processes)并发模型:Go语言特有且推荐使用的。
Go的CSP并发模型,是通过goroutine和channel来实现的。
不同于传统的多线程通过共享内存来通信,CSP讲究的是“以通信的方式来共享内存”。
普通的线程并发模型,就是像Java、C++、或者Python,他们线程间通信都是通过共享内存的方式来进行的。非常典型的方式就是,在访问共享数据(例如数组、Map、或者某个结构体或对象)的时候,通过锁来访问,因此,在很多时候,衍生出一种方便操作的数据结构,叫做“线程安全的数据结构”。
Go的CSP并发模型,是通过goroutine和channel来实现的。
goroutine 是Go语言中并发的执行单位。可以理解为用户空间的线程。
channel是Go语言中不同goroutine之间的通信机制,即各个goroutine之间通信的”管道“,有点类似于Linux中的管道。
M:是内核线程
P : 是调度协调,用于协调M和G的执行,内核线程只有拿到了 P才能对goroutine继续调度执行,一般都是通过限定P的个数来控制golang的并发度
G : 是待执行的goroutine,包含这个goroutine的栈空间
Gn : 灰色背景的Gn 是已经挂起的goroutine,它们被添加到了执行队列中,然后需要等待网络IO的goroutine,当P通过 epoll查询到特定的fd的时候,会重新调度起对应的,正在挂起的goroutine。
Golang为了调度的公平性,在调度器加入了steal working 算法 ,在一个P自己的执行队列,处理完之后,它会先到全局的执行队列中偷G进行处理,如果没有的话,再会到其他P的执行队列中抢G来进行处理
csp很好的例子:生产者和消费者。
10、说一下go中channel是不是线程安全的,什么情况下会变成线程不安全
Channel是Go中的一个核心类型,可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯(communication),Channel也可以理解是一个先进先出的队列,通过管道进行通信。
Golang的Channel,发送一个数据到Channel 和 从Channel接收一个数据 都是