一、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题库中的简单题和中等难度题)

四、数据库相关知识

五、开放性问题:如你在工作中有遇到什么比较难的问题,你是怎么解决的?你平常是怎么学习的?



码字不易,给个赞吧!