基础提问:
GO是什么?
Go(又称Golang)是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。
使用Go编程有什么好处?
Go 语言速度非常快 可直接编译成机器码,不依赖其他库
易于掌握 与其他语言相比,Go 语言的语法很简单,很容易掌握。
静态类型定义语言 Go 是一种强大的静态类型定义语言。有基本类型,也有结构类型,还具有内置的列表和映射类型,而且它们也易于使用。
接口类型 Go 语言有接口类型,任何结构都可以简单地通过实现接口的方法来满足接口。
标准库 Go 语言有一个相当不错的标准库。它提供了方便的内置函数,用于处理基本类型。有些包可以让你轻松构建一个 Web 服务器,标准库提供的 JSON 序列化和反序列化非常简单。
测试支持 测试支持内置在标准库中,不需要额外的依赖。如果你有个名为 thing.go 的文件,请在另一个名为 thing_test.go 的文件中编写测试,并运行“go test”。Go 就将快速执行这些测试。
垃圾收集 在设计 Go 语言时,有意将内存管理设计得比 C 和 C++ 更容易。动态分配的对象是垃圾收集。Go 语言使指针的使用更加安全,因为它不允许指针运算。还提供了使用值类型的选项。
天生的支持并发 虽然并发编程从来就不是一件易事,但 Go 语言在并发编程要比其他语言更容易。创建一个名为“goroutine”的轻量级线程,并通过“channel”与它进行通信几乎是非常简单的事情
Go支持类型继承吗?
go语言是不支持类继承的,接口正是Go语言里面实现多态的方法
不支持方法重载,不支持泛型,区分大小写
Go支持指针算术吗? 支持除±运算的算术
Go中变量的动态类型声明是什么?
用var申明变量
var x interface{} // x 为零值 nil,静态类型为 interface{}
var v *T // v 为零值 nil, 静态类型为 *T
x = 42 // x 的值为 42,动态类型为int, 静态类型为interface{}
x = v // x 的值为 (*T)(nil), 动态类型为 *T, 静态类型为 *T
你能在Go中的单个声明中声明多种类型的变量吗?
能 可以将若干个需要声明的变量放置在一起,免得程序员需要重复 写var关键字
如何在Go中打印变量的类型?
直接使用reflect的TypeOf方法就可以了
什么是指针?
指针就是一个存放地址的变量当指针指向某个变量这时这个指针里就存放了那个变量的地址同时可以利用指针直接取变量中的值用 只要在指针前加 * 就是取其真值了
继续声明的目的是什么?
赋值变量新值
goto语句的目的是什么?
无条件转移语句,它通常与条件语句配合使用来改变程序流向,使得程序转去执行语句标号所标识的语句
Go的函数结构是什么?
func func_name (参数,参数类型) (返回值,类型){ 函数体}
将参数传递给函数的默认方式是什么?
不支持默认参数 不支持命名实参 参数视作为函数的局部变量 必须按签名顺序传递指定类型和数量的实参。相邻的同类型参数可以合并支持不定长变参
Go中的函数作为值是什么意思?
在Go中函数也是一种变量,我们可以通过type来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型
type typeName func(input1 Type1 , input2 Type2 [, ...]) (result1 Type1 [, ...])
Go中的方法是什么?
定义的或内置的可调用函数
解释Printf()函数的用途。
格式化输出
Print: 输出到控制台(不接受任何格式化,它等价于对每一个操作数都应用 %v)
fmt.Print(str)
Println: 输出到控制台并换行
fmt.Println(tmp)
Printf : 只可以打印出格式化的字符串。只可以直接输出字符串类型的变量(不可以输出整形变量和整形 等)
fmt.Printf("%d",a)
Sprintf:格式化并返回一个字符串而不带任何输出。
s := fmt.Sprintf("a %s", "string") fmt.Printf(s)
Fprintf:来格式化并输出到 io.Writers 而不是 os.Stdout。
fmt.Fprintf(os.Stderr, “an %sn”, “error”)
解释模块化编程
将一个项目按照功能划分,理论上一个功能一个模块,互不影响,在需要的时候载入
谈谈对指针的理解
变量存储的是一个值,但是这个值在内存中有一个地址,而指针保存的就是这个地址,通过这个地址,可以获取到值。 golang 的函数, 所有的参数都是传递一个复制的值. 如果值的体积过大, 那么就会严重降低效率, 而传递一个地址, 就会大大提高效率.
go什么情况下会发生内存泄漏?
给系统打压力,内存占用增大,但停止打压后,内存不能降低,则可能有内存泄漏。
go为什么高并发好?
goroutine是Go并行设计的核心,它的本质是协程,它比线程更小,占用内存更小,这是它能做到高并发的前提
中级水平面试题:
1.什么是channel,为什么它可以做到线程安全?
Channel是Go中的一个核心类型,可以理解是一个先进先出的队列,可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯。而且Go的设计思想就是:不要通过共享内存来通信,而是通过通信来共享内存,前者就是传统的加锁,后者就是Channel。也就是说,设计Channel的主要目的就是在多任务间传递数据的,这当然是安全的。
2.无缓冲 Chan 的发送和接收是否同步?
ch := make(chan int) 无缓冲的channel由于没有缓冲发送和接收需要同步.
ch := make(chan int, 2) 有缓冲channel不要求发送和接收操作同步.
3. go语言的并发机制以及它所使用的CSP并发模型
Golang的CSP并发模型,是通过Goroutine和Channel来实现的,用于描述两个独立的并发实体“以通信的方式来共享内存”。 CSP中channel是第一类对象,它不关注发送消息的实体,而关注与发送消息时使用的channel。
Golang中channel 是被单独创建并且可以在进程之间传递,它的通信模式类似于 boss-worker 模式的,一个实体通过将消息发送到channel 中,然后又监听这个 channel 的实体处理,两个实体之间是匿名的,在实现原理上其实类似一个阻塞的消息队列。
Goroutine 是Golang实际并发执行的实体,它底层是使用协程(coroutine)实现并发,go底层选择使用coroutine的出发点是因为,它具有以下特点:
- 用户空间 避免了内核态和用户态的切换导致的成本。
- 可以由语言和框架层进行调度。
- 更小的栈空间允许创建大量的实例。
Channel是Go语言中各个并发结构体(Goroutine)之前的通信机制。通常Channel,是各个Goroutine之间通信的”管道“,有点类似于Linux中的管道。
而且不管传还是取,必阻塞,直到另外的goroutine传或者取为止
4.Golang 中常用的并发模型?
-
通过channel通知实现并发控制
无缓冲的通道指的是通道的大小为0,也就是说,这种类型的通道在接收前没有能力保存任何值,它要求发送 goroutine 和接收 goroutine 同时准备好,才可以完成发送和接收操作,如果没有同时准备好的话,先执行的操作就会阻塞等待,直到另一个相对应的操作准备好为止。这种无缓冲的通道我们也称之为同步通道。 -
通过sync包中的WaitGroup实现并发控制
Goroutine是异步执行的,有的时候为了防止在结束mian函数的时候结束掉Goroutine,所以需要同步等待,这个时候就需要用 WaitGroup了,在 sync 包中,提供了 WaitGroup ,它会等待它收集的所有 goroutine 任务全部完成。在WaitGroup里主要有三个方法:
1.Add, 可以添加或减少 goroutine的数量.
2.Done, 相当于Add(-1).
3.Wait, 执行后会堵塞主线程,直到WaitGroup 里的值减至0. -
在Go 1.7 以后引进的强大的Context上下文,实现并发控制
通常,在一些简单场景下使用 channel 和 WaitGroup 已经足够了,但是当面临一些复杂多变的网络并发场景下,比如一个网络请求 Request,每个 Request 都需要开启一个 goroutine 做一些事情,这些 goroutine 又可能会开启其他的 goroutine,比如数据库和RPC服务。 所以我们需要一种可以跟踪 goroutine 的方案,才可以达到控制他们的目的,这就是Go语言为我们提供的 Context,称之为上下文非常贴切,它就是goroutine 的上下文。 它是包括一个程序的运行环境、现场和快照等。每个程序要运行时,都需要知道当前程序的运行状态,通常Go 将这些封装在一个 Context 里,再将它传给要执行的 goroutine 。
context 包主要是用来处理多个 goroutine 之间共享数据,及多个 goroutine 的管理。
5.Golang GC 垃圾回收机制
常用的垃圾回收机制:
-
引用计数:每个对象维护一个引用计数器,当引用该对象的对象被销毁或者更新的时候,被引用对象的引用计数器自动减 1,当被应用的对象被创建,或者赋值给其他对象时,引用 +1,引用为 0 的时候回收,思路简单,但是频繁更新引用计数器降低性能,存在循环以引用(php,Python所使用的)
-
标记清除:就是 golang 所使用的,从根变量来时遍历所有被引用对象,标记之后进行清除操作,对未标记对象进行回收,缺点:每次垃圾回收的时候都会暂停所有的正常运行的代码,系统的响应能力会大大降低,各种 mark&swamp 变种(三色标记法),缓解性能问题。
-
分代搜集:分代收集的基本思想是,将堆划分为两个或多个称为代(generation)的空间。新创建的对象存放在称为新生代(young generation)中(一般来说,新生代的大小会比 老年代小很多),随着垃圾回收的重复执行,生命周期较长的对象会被提升(promotion)到老年代中(这里用到了一个分类的思路,这个是也是科学思考的一个基本思路)。
gc的过程一共分为四个阶段:
- 栈扫描(开始时STW)
- 第一次标记(并发)
- 第二次标记(STW)
- 清除(并发)
6.Golang 中 Goroutine 如何调度?
goroutine的本质是协程,只需使用go关键字(例如:go func())即可启动一个协程,并且它是处于异步方式运行
7. 并发编程概念是什么?
并发:同一段时间内运行多个任务,各任务之间交替执行。线程并发
并行:是指两个或者多个事件在同一时刻发生,进程并行
并发编程的目标是充分的利用处理器的多核性能,以达到最高的处理性能。
8. 负载均衡原理是什么?
负载均衡是建立在现有网络结构之上,通常用于将工作负载分布到多个服务器来提高系统稳定性能和可靠性,从而共同完成工作任务。
9.微服务架构是什么样子的?
通常传统的项目体积庞大,需求、设计、开发、测试、部署流程固定。新功能需要在原项目上做修改。
但是微服务可以看做是对大项目的拆分,是在快速迭代更新上线的需求下产生的。新的功能模块会发布成新的服务组件,与其他已发布的服务组件一同协作。 服务内部有多个生产者和消费者,通常以http rest的方式调用,系统总体以一个(或几个)服务的形式呈现给客户使用。
微服务架构是一种思想对微服务架构我们没有一个明确的定义,但简单来说微服务架构是:
采用一组服务的方式来构建一个应用,服务独立部署在不同的进程中,不同服务通过一些轻量级交互机制来通信,例如 RPC、HTTP 等,服务可独立扩展伸缩,每个服务定义了明确的边界,不同的服务甚至可以采用不同的编程语言来实现,由独立的团队来维护。
微服务架构设计包括:
- 服务熔断降级限流机制
- 框架调用方式解耦方式
- 链路监控
- 多级缓存.
- 网关 (kong gateway).
- Docker部署管理 Kubenetters.
- 自动集成部署 CI/CD 实践.
- 自动扩容机制规则.
- 压测 优化.
- Trasport 数据传输(序列化和反序列化).
- Logging 日志.
10分布式锁实现原理,用过吗?
通常分布式锁以单独的服务方式实现,目前比较常用的分布式锁实现有三种:
- 基于数据库实现分布式锁。
- 基于缓存(redis,memcached,tair)实现分布式锁。
- 基于Zookeeper实现分布式锁。
选用Redis实现分布式锁原因:
- Redis有很高的性能;
- Redis命令对此支持较好,实现起来比较方便
主要实现方式:
- SET lock currentTime+expireTime EX 600 NX,使用set设置lock值,并设置过期时间为600秒,如果成功,则获取锁;
- 获取锁后,如果该节点掉线,则到过期时间lock值自动失效;
- 释放锁时,使用del删除lock键值;
缺点:使用redis单机来做分布式锁服务,可能会出现单点问题,导致服务可用性差,因此在服务稳定性要求高的场合,官方建议使用redis集群
优点: 性能高,redis可持久化,也能保证数据不易丢失,redis集群方式提高稳定性