一、Golang基础知识的考察
1、数组和切片的区别?
从语法上来看,数组遵循传统的三要素 – 名称、类型、长度。数组的一切传递都是值拷贝。
而切片只有名称、类型,这意味着切片是不定长的。
从内存的角度来看,数组是一整块连续的、固定长度、固定位置的内存。
而切片则是一个指针,指向一块内存,当容量不够时就开辟更大的内存。
切片扩容:slice在append时如果超出了原来的容量时会翻倍扩容
//原切片长度低于1024时直接翻倍
//原切片长度大于等于1024时,每次只增加25%,直到满足需要的容量
2、goroutine、线程和进程的区别?
进程是什么呢?
直白地讲,进程就是应用程序的启动实例。比如我们运行一个游戏,打开一个软件,就是开启了一个进程。
进程拥有代码和打开的文件资源、数据资源、独立的内存空间。
线程又是什么呢?
线程从属于进程,是程序的实际执行者。一个进程至少包含一个主线程,也可以有更多的子线程。
线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是这样的)。
1 进程是资源分配的单位
2 线程是操作系统调度的单位
3 进程切换需要的资源很最大,效率很低
4 线程切换需要的资源一般,效率一般
5 协程切换任务资源很小,效率高
6 多进程、多线程根据cpu核数不一样可能是并行的 也可能是并发的。
协程的本质就是使用当前进程在不同的函数代码中切换执行,可以理解为并行。 协程是一个用户层面的概念,不同协程的模型实现可能是单线程,也可能是多线程。
协程和线程一样共享堆,不共享栈。
一个应用程序一般对应一个进程,一个进程一般有一个主线程,还有若干个辅助线程,线程之间是平行运行的,在线程里面可以开启协程,让程序在特定的时间内运行。
协程和线程的区别是:协程避免了无意义的调度,由此可以提高性能。
3、go中哪些数据类型是值类型?哪些数据类型是引用类型?哪些类型可以比较?哪些类型不可以比较?
值类型:int、float、bool、sturct等
引用类型有:数组、切片、map、channel、interface。
4、nil是关键字吗?nil可以比较吗?
1. nil 标识符是不能比较的
2. nil 不是关键字或保留字
3. nil 没有默认类型
4. 不同类型 nil 的指针是一样的
5. 不同类型的 nil 是不能比较的
6. 两个相同类型的 nil 值也可能无法比较
7. nil 是 map、slice、pointer、channel、func、interface 的零值
8. 不同类型的 nil 值占用的内存大小可能是不一样的
Gomapslicefunctionnil
5、make和new的区别?
new:用来初始化泛型,并且返回指针存储的位置。
make:用来初始化一些特别的类型,如slice、map、channel,返回没有指针。
6、有缓冲的channel和无缓冲的channel的区别?
无缓冲通道:cap()和len()都是0。用于通信和goroutine同步。
有缓冲通道:len代表没有读取的元素数,cap代表整个通道的容量。主要用于通信。
goroutine退出后,写到缓冲通道中的数据不会消失,它可以缓冲和适配两个goroutine处理速度不一致的情况,缓冲通道和消息队列类似,有削峰和增大吞吐量的功能。
7、赋值操作是原子操作吗?
不一定是,还跟上下文有关,如果上下文是一个没有并发进程的程序,那么该代码在该上下文中就是原子的。
8、内置map和syn.map的区别?
内置map在并发情况下,只读是线程安全的,同时写线程不安全,所以为了并发安全 & 高效,官方实现了一把。
sync.Map也是在golang提供的map关键字之上封装实现的。
sync.Map 整体的优化可以描述为以下几点:
空间换时间。 通过冗余的两个数据结构(read、dirty),实现加锁对性能的影响。
优先从read读取、更新、删除,因为对read的读取不需要锁。map只保存key和对应的value的指针,这样可以并发的读写map, 实际更新指向value的指针再通过基于CAS的无锁atomic。
使用只读数据(read),避免读写冲突
动态调整,miss次数多了之后,将dirty数据提升为read。
double-checking。
延迟删除。 删除一个键值只是打标记,只有在提升dirty的时候才清理删除的数据。
建议阅读一下sync.Map的地产代码实现。
9、互斥锁和读写锁的区别?
1、互斥锁
互斥锁能够保证在同一时间段内仅有一个goroutine持有锁,即保证某一时间段内有且仅有一个goroutine访问共享资源,其他申请锁的goroutine将会被阻塞直到锁释放,然后重新争抢锁的持有权。
有两种工作模式:一种正常模式:排队等待资源。二是饥饿模式:抢夺模式。
其中Mutex为互斥锁,Lock()加锁,Unlock()解锁,使用Lock()加锁后,便不能再次对其进行加锁,直到利用Unlock()解锁对其解锁后,才能再次加锁.适用于读写不确定场景,即读写次数没有明显的区别,并且只允许只有一个读或者写的场景,所以该锁也叫做全局锁。
func (m *Mutex) Unlock()用于解锁m,如果在使用Unlock()前未加锁,就会引起一个运行错误.已经锁定的Mutex并不与特定的goroutine相关联,这样可以利用一个goroutine对其加锁,再利用其他goroutine对其解锁。
互斥锁只能锁定一次,当在解锁之前再次进行加锁,便会死锁状态,如果在加锁前解锁,便会报错“panic: sync: unlock of unlocked mutex”
2、读写锁
同一时间段只能有一个goroutine获取到写锁。
同一时间段可以有人员多个goroutine获取到读锁。
同一时间段内只能存在写锁或读锁(写锁和读锁互斥)
RWMutex是一个读写锁,该锁可以加多个读锁或者一个写锁,其经常用于读次数远远多于写次数的场景.
写锁:信号量加一个非常大的数值,释放写锁:减去一个极大的数值。
读锁:信号量加1,读锁解锁:信号量减1.
func (rw *RWMutex) Lock() 写锁,如果在添加写锁之前已经有其他的读锁和写锁,则lock就会阻塞直到该锁可用,为确保该锁最终可用,已阻塞的 Lock 调用会从获得的锁中排除新的读取器,即写锁权限高于读锁,有写锁时优先进行写锁定
func (rw *RWMutex) Unlock() 写锁解锁,如果没有进行写锁定,则就会引起一个运行时错误
func (rw *RWMutex) RLock() 读锁,当有写锁时,无法加载读锁,当只有读锁或者没有锁时,可以加载读锁,读锁可以加载多个,所以适用于"读多写少"的场景
func (rw *RWMutex)RUnlock() 读锁解锁,RUnlock 撤销单次RLock 调用,它对于其它同时存在的读取器则没有效果。若 rw 并没有为读取而锁定,调用 RUnlock 就会引发一个运行时错误(注:这种说法在go1.3版本中是不对的,例如下面这个例子)。
读写锁的写锁只能锁定一次,解锁前不能多次锁定,读锁可以多次,但读解锁次数最多只能比读锁次数多一次,一般情况下我们不建议读解锁次数多余读锁次数
10、两个结构体可以比较吗?
1、两个结构体无论类型是否相同,是否包含不可比较数据类型,都可以通过reflect.DeepEqual(struct1,struct2)进行比较
2、若两结构体是同一个结构体类型,且该结构体类型中不包含map,slice等不可比较数据类型,那么这两个结构体可以使用“==”进行比较是否相等。
二、计算机网络通信知识
三、算法(LeetCode题库中的简单题和中等难度题)
四、数据库相关知识
五、开放性问题:如你在工作中有遇到什么比较难的问题,你是怎么解决的?你平常是怎么学习的?