🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## 一、说说go语言的main函数 (1)、main函数不能带参数。 (2)、main函数不能定义返回值。 (3)、main函数所在的包必须为main包。 (4)、main函数中可以使用flag包来获取和解析命令行参数。 ## 二、在go语言中,new和make的区别? #### 相关知识点: new和make是内建的两个函数,主要用来创建分配类型内存。 #### 1、new函数是内建函数,函数定义为 ``` func new(Type) *Type ``` new 的作用是初始化一个指向类型的指针(*Type ),使用new函数来分配空间。传递给new 函数的是一个类型,不是一个值。返回值是 指向这个新分配的零值的指针。 #### 实例 ~~~ package main import ( "fmt" ) func main() { a := new([]int) fmt.Println(a) //输出&[],a本身是一个地址 } ~~~ 运行结果: &[] #### 实例2 ~~~ package main import "fmt" func main() { var i *int fmt.Println("没有new之前I得值为:", i) i = new(int) fmt.Println("new之后I得值为:", i) } ~~~ 执行结果: ``` 没有new之前I得值为: <nil> new之后I得值为: 0xc0000aa058 ``` #### 2、make函数是内建函数,函数定义为 #### 定义 `make`也是用于内存分配的,make(T, args)函数的目的与new(T)不同,make只用于`chan`、`map`以及slice(切片)的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。 ``` func make(t Type, size ...IntegerType) Type ``` 第一个参数是一个类型,第二个参数是长度。 make返回类型是 T(不是T*)的一个初始化的(不是零值)的实例。 make实例: ~~~ var slice1 []type = make([]type, len) 也可以简写为 slice1 := make([]type, len) ~~~ 实例: ~~~ package main import ( "fmt" ) func main() { b := make([]int, 1) fmt.Println(b) //输出[0],b本身是一个slice对象,其内容默认为0 } ~~~ 运行结果: [0] #### 注意: a、简单来说,new只是分配内存,不初始化内存; 而make即分配又初始化内存。所谓的初始化就是给类型赋初值,比如字符为空,整型为0, 逻辑值为false等。 b、当对slice,map以及channel进行初始化时,使用make比new方式要好,而其他形式的则利用new进行初始化 c、new 的作用是初始化一个指向类型的指针(*T),make 的作用是为 slice,map 或 chan 初始化并返回引用(T)。 d、二者都是内存的分配(堆上),但是`make`只用于slice、map以及channel的初始化(非零值);而`new`用于类型的内存分配,并且内存置为零。 e、`make`返回的还是这三个引用类型本身;而`new`返回的是指向类型的指针。 ## 三、go语言中指针运算有哪些? 1、可以通过“&”取指针的地址。 2、可以通过“\*”取指针指向的数据。 ## 四、说说go语言中的协程? 1、协程和线程都可以实现程序的并发执行; 2、通过channel来进行协程间的通信; 3、只需要在函数调用前添加go关键字即可实现go的协程,创建并发任务; 4、关键字go并非执行并发任务,而是创建一个并发任务单元; #### 协程实例 ## 五、go语言中的引用类型包含哪些? 数组切片(slice)、字典(map)、通道(channel)、接口(interface) ## 六、说说go语言中的init函数?* 1、一个包中,可以包含多个init函数 2、程序编译时,先执行导入包的init函数,再执行本包内的init函数 ## 七、说说go语言的同步锁? 1、当一个goroutine获得了Mutex后,其他goroutine就只能乖乖的等待,除非该goroutine释放这个Mutex 2、 RWMutex在读锁占用的情况下,会阻止写,但不阻止读 3、RWMutex在写锁占用情况下,会阻止任何其他goroutine(无论读和写)进来,整个锁相当于由该goroutine独占 ## 八、说说go语言的关于go vendor? 1、基本思路是将引用的外部包的源代码放在当前工程的vendor目录下面 2、编译go代码会优先从vendor目录先寻找依赖包 3、有了vendor目录后,打包当前的工程代码到其他机器的$GOPATH/src下都可以通过编译 ## 九、go之无缓冲channel(通道)和有缓冲channel(通道) #### 1、channel定义: * channel中的一个核心类型,可以把它看成管道。并发核心单元通过它就可以发送或者接收数据进行通讯,这在一定程度上又进一步降低了编程的难度。 * channel是一个数据类型,主要用来解决go程的同步问题以及协程之间数据共享(数据传递)的问题。 * channel和map类似,channel也一个对应make创建的底层数据结构的引用。 * 和其它的引用类型一样,channel的零值也是nil。 * 定义一个channel时,也需要定义发送到channel的值的类型。channel可以使用内置的make()函数来创建 #### 2、go之无缓冲channel(通道) ~~~ package main import ( "fmt" ) func main() { ch := make(chan int) //这里就是创建了一个channel,这是无缓冲管道注意 go func() { //创建子go程 for i := 0; i < 6; i++ { ch <- i //循环写入管道 fmt.Println("写入", i) } }() for i := 0; i < 6; i++ { //主go程 num := <-ch //循环读出管道 fmt.Println("读出", num) } } ~~~ 执行结果: 写入 0 读出 0 读出 1 写入 1 写入 2 读出 2 读出 3 写入 3 写入 4 读出 4 读出 5 我们在代码中 先创建了一个匿名函数的子go程,和main的主go程一起争夺cpu,但是我们在里面创建了一个管道,无缓冲管道有一个规则那就是必须读写同时操作才会有效果,如果只进行读或者只进行写那么会被阻塞,被暂时停顿等待另外一方的操作,在这里我们定义了一个容量为0的通道,这就是无缓冲通道,如下图 ![](https://img.kancloud.cn/38/02/38027fafb593aa2bbec30c33f3b4afd7_858x796.png) 无缓冲通道的特点: * 一次只能传输一个数据 * 同一时刻,同时有 读、写两端把持 channel。 * 如果只有读端,没有写端,那么 “读端”阻塞。 * 如果只有写端,没有读端,那么 “写端”阻塞。 * 读channel: <- channel * 写channel: channel <- 数据 #### 2、go有缓冲channel(通道) #### 工作原理: ![](https://img.kancloud.cn/83/cd/83cdd74eafb033800a18227e7412b7b2_796x534.png) #### 流程: * 第 1 步,右侧的 goroutine 正在从通道接收一个值。 * 第 2 步,右侧的这个 goroutine独立完成了接收值的动作,而左侧的 goroutine 正在发送一个新值到通道里。 * 第 3 步,左侧的goroutine 还在向通道发送新值,而右侧的 goroutine 正在从通道接收另外一个值。这个步骤里的两个操作既不是同步的,也不会互相阻塞。 * 第 4 步,所有的发送和接收都完成,而通道里还有几个值,也有一些空间可以存更多的值。 #### 特点: * 有缓冲通道就是图中所示,一方可以写入很多数据,不用等对方的操作,而另外一方也可以直接拿出数据,不需要等对方写,但是注意一点(如果写入的一方把channel写满了,那么如果要继续写就要等对方取数据后才能继续写入,这也是一种阻塞,读出数据也是一样,如果里面没有数据则不能取,就要等对方写入),而有缓冲channel定义也比较简单 ``` ch:=make(chan int,10)//chan int 只能写入读入int类型的数据,10代表容量为10. ``` #### 实例 ~~~ package main import ( "fmt" ) func main() { ch := make(chan int, 100) //这里就是创建了一个channel,这是有缓冲管道 go func() { //创建子go程 for i := 0; i < 10; i++ { ch <- i //循环写入管道 fmt.Println("写入", i) } }() for { //主go程 num := <-ch //循环读出管道 fmt.Println("读出", num) } } ~~~ 执行结果: ``` 写入 0 写入 1 写入 2 写入 3 写入 4 写入 5 写入 6 写入 7 写入 8 写入 9 读出 0 读出 1 读出 2 读出 3 读出 4 读出 5 读出 6 读出 7 读出 8 读出 9 fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive]: main.main() D:/diu/model03.go:16 +0x88 exit status 2 ``` 通过实例我们可以看到产生了死锁 #### 实例2(解决有缓存通道的死锁) #### 方式一: ~~~ package main import ( "fmt" ) func main() { ch := make(chan int, 100) //这里就是创建了一个channel,这是有缓冲管道 go func() { //创建子go程 for i := 0; i < 10; i++ { ch <- i //循环写入管道 fmt.Println("写入", i) } }() for i := 0; i < 10; i++ { //主go程 num := <-ch //循环读出管道 fmt.Println("读出", num) } } ~~~ 执行结果: ``` 写入 0 写入 1 写入 2 写入 3 写入 4 写入 5 写入 6 写入 7 写入 8 写入 9 读出 0 读出 1 读出 2 读出 3 读出 4 读出 5 读出 6 读出 7 读出 8 读出 9 ``` #### 方式二: #### 使用实例说明有缓冲channel和无缓冲channel * 同步通信: 数据发送端,和数据接收端,必须同时在线。 —— 无缓冲channel 打电话。打电话只有等对方接收才会通,要不然只能阻塞 * 异步通信:数据发送端,发送完数据,立即返回。数据接收端有可能立即读取,也可能延迟处理。 —— 有缓冲channel 不用等对方接受,只需发送过去就行 发信息。短信。 ## 十、说说go语言的channel特性? 1、给一个 nil channel 发送数据,造成永远阻塞 2、 从一个 nil channel 接收数据,造成永远阻塞 3 、给一个已经关闭的 channel 发送数据,引起 panic 4、从一个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回一个零值 5、无缓冲的channel是同步的,而有缓冲的channel是非同步的 ## 十一、说说go语言的select机制? 1、select机制用来处理异步IO问题 2、select机制最大的一条限制就是每个case语句里必须是一个IO操作 3、golang在语言级别支持select关键字 ## 十二、协程,线程,进程的区别? #### 1、进程 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。 #### 2、线程 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。 #### 3、协程 协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。 ## 十三、并发编程概念是什么? 并行是指两个或者多个事件在同一时刻发生;并发是指两个或多个事件在同一时间间隔发生。 发偏重于多个任务交替执行,而多个任务之间有可能还是串行的。而并行是真正意义上的“同时执行” 并发编程是指在一台处理器上“同时”处理多个任务。并发是在同一实体上的多个事件。多个事件在同一时间间隔发生。并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能。 ## 十四、读写锁或者互斥锁读的时候能写吗? Go中读写锁包括读锁和写锁,多个读线程可以同时访问共享数据;写线程必须等待所有读线程都释放锁以后,才能取得锁;同样的,读线程必须等待写线程释放锁后,才能取得锁,也就是说读写锁要确保的是如下互斥关系,可以同时读,但是读-写,写-写都是互斥的。