golang面试题题目归纳
golang面试题题目归纳
- 2021.06.01
- defer
- 2021.06.02
- go的调度
- 1.多进程/多线程产生的问题
- 2、 “内核态 “线程和” 用户态 “线程
- 映射关系
- 3、Go语言的协程goroutine
- 4、被废弃的goroutine调度器
- 5、Goroutine调度器的GMP模型的设计思想
- GMP模型
- 调度器的生命周期
- 2021.06.06
- go struct能不能比较
- select
- context包的用途
- client如何实现长连接
- 主协程如何等其余协程完再操作
- slice,len,cap,共享,扩容
- map如何顺序读取
- 实现set
- 2021.06.07
- 实现消息队列(多生产者,多消费者)
- 大文件排序
- 基本排序,哪些是稳定的
- HTTP GET和Head
- HTTP keep-alive
- HTTP能不能一次连接多次请求,不等后端返回
- TCP和UDP的区别,UDP有点,适用场景
- 2021.06.09
- time-wait的作用
- 数据库如何建索引
- 孤儿进程,僵尸进程
- 2021.06.11
- 死锁条件,如何避免
- linux命令,查看端口占用,cpu负载,内存占用,如何发送信号给一个进程
- 2021.06.12
- Slice与数组的区别,Slice底层结构
- redis的基本数据结构
- Redis的List用过吗?底层怎么实现的?
- 2021.06.17
- MySQL的索引有几种,以及其时间复杂度
- InnoDB是表锁还是行锁,为什么
- Go的channel(有缓冲无缓冲)
- 用过什么消息中间件
- 退出程序怎么防止无缓冲channel没有消费完(channel还留有数据没有)
- go 生产者消费者
- 手写循环队列
2021.06.01
defer
下面这段代码输出的内容
package mainimport ("fmt")func main() {defer_call()}func defer_call() {defer func() { fmt.Println("打印前") }()defer func() { fmt.Println("打印中") }()defer func() { fmt.Println("打印后") }()panic("触发异常")
}
打印后
打印中
打印前
panic:触发异常
ps:defer的执行顺序是先进后出,当出现panic时,会先按照defer的后进先出的顺序执行,最后才会执行panic
2021.06.02
go的调度
1.多进程/多线程产生的问题
- 高内存占用
每个任务都创建一个线程是不现实的,会消耗大量的内存 - 调度的高消耗CPU
程拥有太多的资源,进程的创建、切换、销毁,都会占用很长的时间,CPU 虽然利用起来了,但如果进程过多,CPU 有很大的一部分都被用来进行进程调度了。
2、 “内核态 “线程和” 用户态 “线程
内核线程 -> 线程
用户线程 -> 协程
映射关系
- N:1关系
N个协程绑定一个线程,优点就是协程在用户态线程即完成切换,不会陷入到内核态,这种切换非常的轻量快速。但也有很大的缺点:一个进程的所有协程都绑定在一个线程上
缺点:
- 某个程序用不了硬件的多核加速能力
- 一旦某协程阻塞,造成线程阻塞,本进程的其他协程都无法执行了,根本就没有并发的能力了
- 1:1关系
一个协程绑定一个线程,这种最容易实现。协程的调度都由CPU完成了,不存在N:1缺点
缺点:
- 协程的创建、删除和切换的代价都由CPU完成,有点略显昂贵了。
- M:N关系
M个协程绑定N个线程,是N:1和1:1类型的结合,克服了以上两种模型的缺点,但实现起来最为复杂
协程跟线程是有区别的,线程由CPU调度是抢占式的,协程由用户态是协作式的,一个协程让出CPU后,才执行下一个协程
3、Go语言的协程goroutine
Go为了提供更容易使用的并发方法,使用了goroutine和channel。goroutine来自协程的概念,让一组可复用的函数运行在一组线程之上,即便有协程阻塞,该线程的其他协程也可以被runtime调度,转移到其他可运行的线程上,最关键的是,程序员看不到这些底层细节,这就降低了变成的难度,提供了更容易的开发
Go中,协程被称为goroutine,它非常轻量,一个goroutine只占几KB,并且这几KB就足够goroutine运行完,这就能在有限的内存空间内支持大量goroutine,支持更多的并发,虽然一个goroutine的栈只占几KB,但实际上是可伸缩的,如果需要更多的内容,runtime会自动为goroutine分配。
goroutine的特点:
- 占用内存更小(几KB,可由runtime动态分配大小,支持动态调节)
- 调度更灵活(runtime调度,非用户调度,解放开发者的变成难度)
4、被废弃的goroutine调度器
Go目前使用的调度器是2012年重新设计的,因为之前的调度器性能存在问题,所以使用4年就被放弃了
G表示goroutine,M表示线程
M想要执行、放回G都必须访问全局G队列,并且M有多个,即多线程访问同一资源需要加锁进行保证互斥/同步,所以全局G队列是有互斥锁进行保护的。
老调度器有几个缺点:
- 创建、销毁、调度G都需要每个M获得锁,这就形成了激烈的锁竞争
- M转移G会造成延迟和额外的系统负载。比如当G中包含创建新协程的时候,M创建了G’,为了继续执行G,需要把G‘交给M’执行,也造成了很差的局部性,因为G‘和G是相关的,最好放在M上执行,而不是其他M‘
- 系统调用(CPU在M之间的切换)导致频繁的线程阻塞和取消阻塞操作增加了系统的开销
5、Goroutine调度器的GMP模型的设计思想
面对之前调度器的问题,Go设计了新的调度器
在新调度器中,除了M(thread)和G(goroutine),又引进了P(processor)
Processor,它包含了运行goroutine的资源,如果线程想运行goroutine,必须先获得P,P中包含了可运行的G队列
GMP模型
在Go中,线程试运行goroutine的实体,调度器的功能是把可运行的goroutine分配到工作线程上
- 全局队列(Global Queue),存放等待运行的G
- P的本地队列:同全局队列类似,存放的也是等待运行的G,存的数量有限,不超过256个。新建G’时,G‘有限加入到P的本地队列,如果队列满了,则会把本地队列中一半的G移动到全局队列
- P列表:所有的P都在程序启动时创建,并保存在数组中,最多有GOMAXPROCS(可配置)个
- M:线程想运行任务就得获取P,从P的本地队列获取G,P队列为空时,M也会尝试从全局队列拿一批G放到P的本地队列,或从其他P的本地队列偷一半放到自己P的本地队列。M运行G,G执行之后,M会从P获取下一个G,不断重复下去
Goroutine调度器和OS调度器时通过M结合起来的,每个M代表了一个内核线程,IS调度器负责把内核线程分配到CPU的核上执行
有关P和M的个数问题
1.P的数量:
由启动时环境变量GOMAXPROCS或者是由runtime的方法GOMAXPROCE()决定。这意味着在程序执行的任意时刻都只有GOMAXPROCS或者是由runtime的方法GOMAXPROCE()决定。这意味着在程序执行的任意时刻都只有GOMAXPROCS或者是由runtime的方法GOMAXPROCE()决定。这意味着在程序执行的任意时刻都只有GOMAXPROCS个goroutine在同时运行
2.M的数量:
go语言本身的限制,go程序启动时,会设置M的最大数量,默认10000,但是内核很难支持这么多的线程数,所以这个限制可以忽略
runtime/debug中的SetMaxThreads函数,设置M的最大数量
一个M阻塞了,会创建新的M
M与P的数量没有绝对关系,一个M阻塞,P就会去创建或者切换另一个M,所以,即使P的默认数量是1,也有可能会创建很多个M出来
P和M如何会被创建
- P何时创建,在确定了P的最大数量n后,运行时系统会根据这个数量创建n个P
- M何时创建,没有足够的M来关联P并运行其中的可运行的G。比如所有的M此时都阻塞住了,而P中还有很多就绪任务,就会去寻找空闲的M,而没有空闲的,就回去创建新的M
调度器的生命周期
特殊的M0和G0
M0
M0是启动程序后的编号为0的主线程,这个M对应的实例会在全局变量runtime.m0中,不需要再heap上分配,M0负责执行初始化操作和启动第一个G,在之后M0就和其他的M一样了
G0
G0是每次启动一个M都会第一个创建的goroutine,G0仅用于负责调度的G,G0不指向任何可执行的函数,每个M都会有一个自己的G0,在调度或系统调用时会使用G0的栈空间,全局变量的Go是M0的Go
示例代码:
package mainimport "fmt"func main() {fmt.Println("Hello,world!")
}
接下来我们来针对上面的代码对调度器里面的结构做一个分析(也会经理如上面代码所示的过程)
- runtime创建最初的线程M0和goroutine G0,并把两者关联
- 调度器初始化,初始化M0、栈、垃圾回收,以及创建和初始化由GOMAXPROCS个P构成的P列表
- 示例代码中的main函数main.main,runtime中也有一个main函数——runtime.main,代码经过编译后,runtime.main会调用main.main,程序启动时会为runtime.main创建goroutine,称它为main.goroutine把,然后把main,routine加入到P的本地列表
- 启动M0,M0已经绑定了P,会从P的本地队列获取G,获取到main goroutine
- G拥有栈,M根据G中的栈信息和调度信息设置运行环境
- M运行G
- G退出,再次回到M获取可运行的G,这样重复下去,知道main.main退出,runtime.main执行defer和panic处理,或调到用runtime.exit退出程序
调度器的生命周期几乎占满了一个Go程序的一生,runtime.main和goroutine执行之前都是为了调度器做准备工作,runtime.main的goroutine运行,才是调度器的真正开始,知道runtime.main结束而结束
2021.06.06
go struct能不能比较
因为Go是强类型语言,所以不同类型的结构不能作比较,但是同一类型的实例值是可以比较的,实例不可以比较是因为指针类型
select
select常用于goroutine的完美退出
golang的select就是监听IO操作,当IO操作发生时,触发相应的动作
每个case语句里必须是一个IO操作,确切的说,应该是一个面向channel的IO操作
context包的用途
Context通常被称作上下文,他是一个比较抽象的概念,其本质,是【上下上下】存在上下层的传递,上会把内容传递给下。在Go语言中,程序单元也就指的是goroutine
client如何实现长连接
server是设置超时时间,for循环遍历
主协程如何等其余协程完再操作
使用channel、select、context、waitgroup
slice,len,cap,共享,扩容
append函数,因为slice底层数据结构是由数组、len、cap组成,所以在使用append扩容时,会查看数组后面有没有连续内存块,有就在后面添加,没有就重新生成一个大的数组
map如何顺序读取
map不能顺序读取,因为map是无序的,想要有序读取,首先的解决的问题就是,把key变为有序的,所以可以把key放入切片对切片进行排序,遍历切片,通过key取值
实现set
type inter interface{}
type Set struct {m map[inter]boolsync.RWMutex
}func New() *Set {return *Set{m:map[inter]bool{}}
}func (s *Set) Add(item inter) {s.Lock()defer s.Unlock()s.m[item]=true
}
2021.06.07
实现消息队列(多生产者,多消费者)
使用切片加锁可以实现
大文件排序
归并排序,分而治之,拆分为小文件
基本排序,哪些是稳定的
稳定的排序:
- 冒泡排序 o(n2)
- 鸡尾酒排序(双向冒泡排序)o(n2)
- 插入排序 o(n2)
- 桶排序 o(n);需要o(k)额外
- 计数排序 o(n+k)
- 归并排序 o(nlogn)
- 原地归并排序 o(n2)
- 二叉树排序 o(nlogn)
HTTP GET和Head
400 bad request 请求报文存在语法错误
401 unauthorized 表示发送的请求需要有通过HTTP认证的认证信息
403 forbidden 表示对请求资源的访问被服务器拒绝
404 not found 表示在服务器上没有找到请求的资源
HTTP keep-alive
client发出的HTTP请求头需要增加Connection:keep-alive字段
web-server端需要能识别connection:keep-alive字段,并且在http的response里指定connection:keep-alive字段,告诉client,我能提供keep-alive服务,并且”应允“client我暂时不会关闭socket链接
HTTP能不能一次连接多次请求,不等后端返回
HTTP本质上是使用socket链接,因此发送请求,接写入tcp缓冲,是可以多次进行的,这也是HTTP是无状态的原因
TCP和UDP的区别,UDP有点,适用场景
tcp传输的是数据流,而UDP是数据包,tcp会经过三次握手,udp不需要
2021.06.09
time-wait的作用
time-wait开始的时间为tcp四次挥手中主动关闭连接方发送完最后一次挥手,也就是ACK=1的信号结束后,主动关闭连接方所处的状态
然后time-wait的的持续时间为2MSL. MSL是Maximum Segment Lifetime,译为“报文最大生存时间”,可为30s,1min或2min。2msl就是2倍的这个时间。工程上为2min,2msl就是4min。但一般根据实际的网络情况进行确定持续时间的长度设计原因:
原因1:为了保证客户端发送的最后一个ack报文段能够到达服务器。因为这最后一个ack确认包可能会丢失,然后服务器就会超时重传第三次挥手的fin信息报,然后客户端再重传一次第四次挥手的ack报文。如果没有这2msl,客户端发送完最后一个ack数据报后直接关闭连接,那么就接收不到服务器超时重传的fin信息报(此处应该是客户端收到一个非法的报文段,而返回一个RST的数据报,表明拒绝此次通信,然后双方就产生异常,而不是收不到。),那么服务器就不能按正常步骤进入close状态。那么就会耗费服务器的资源。当网络中存在大量的timewait状态,那么服务器的压力可想而知。
原因2:在第四次挥手后,经过2msl的时间足以让本次连接产生的所有报文段都从网络中消失,这样下一次新的连接中就肯定不会出现旧连接的报文段了。也就是防止我们上一篇文章 为什么tcp是三次握手而不是两次握手? 中说的:已经失效的连接请求报文段出现在本次连接中。如果没有的话就可能这样:这次连接一挥手完马上就结束了,没有timewait。这次连接中有个迷失在网络中的syn包,然后下次连接又马上开始,下个连接发送syn包,迷失的syn包忽然又到达了对面,所以对面可能同时收到或者不同时间收到请求连接的syn包,然后就出现问题了。
数据库如何建索引
普通索引
创建索引:CREATE INDEX [indexName] ON [table_name] ([column_name])
修改表结构:ALTER table [tableName] ADD INDEX indexName
创建表时指定索引:
create table [tableName] (
ID int not null,
username varchar(16) not null,
index [indexName] (username(length))
)唯一索引
创建索引:CREATE UNIQUE INDEX [indexName] ON mytable
修改表结构:ALTER table mytable ADD UNIQUE [indexName] (username(length))
创建表时指定索引:
create table [tableName] (
ID int not null,
username varchar(16) not null,
unique [indexName] (username(length))
)
孤儿进程,僵尸进程
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。
2021.06.11
死锁条件,如何避免
死锁的条件:
- 系统资源不足
- 进程运行推进的顺序不合适
- 资源分配不当等
死锁产生的四个必要条件
- 互斥
- 持有并等待
- 不可抢占
- 环路等待
如何避免:
- 打破互斥条件
- 打破不可抢占条件
- 打破占有且申请条件
- 打破循环等待条件
linux命令,查看端口占用,cpu负载,内存占用,如何发送信号给一个进程
查看端口占用:
- isof -i XXXX(端口号)
- netstat -anp|grep 9001
2.1 netstat命令解释:
netstat -ntlp // 查看当前所有tcp端口
netstat -ntulp | grep 80 // 查看所有80端口使用情况
netstat -an | grep 3306 // 查看所有3306端口使用情况
参数说明:
-t (tcp)仅显示tcp相关选项
-u (udp)仅显示udp相关选项
-n 拒绝显示别名,能显示数据的全部转化为数字
-l 仅列出在Listen(监听)的服务状态
-p 显示建立相关链接的程序名查看cpu负载:
- atop
- top
- uptime
查看内存占用:
- top
- free
- cat 、proc/meminfo
如何发送信号给一个进程
kill
2021.06.12
Slice与数组的区别,Slice底层结构
区别
数组:
- 长度不可变,初始化的时候需要声明数组的长度
Slice:
- Slice长度不定,append时会自动扩容,属于引用类型
Slice底层结构:
type slice struct {
array unsafe.Pointer
len int
cap int
}
Slice的结构由三部分组成,Pointer是一个指向数组的指针,len表示长度,cap表示容量,cap总是且必须大于len
redis的基本数据结构
string——字符串
redis最简单的数据结构;动态字符串,采用与分配冗余空间的方法
hash——字典
hashmap,哈希
list——列表
其实就是链表;Redis 还提供了操作 List 中某一段元素的 API,你可以直接查询,删除 List 中某一段的元素
set——集合
集合就是一堆不重复值的组合
sorted list——有序集合
比普通set多了一个权重score,使集合中的元素能够按照score进行有序排序
Redis的List用过吗?底层怎么实现的?
没用过,但是了解过
底层是通过链表(双向和压缩)来实现的
链表与数组最大的区别就是链表是链式存储,数组是连续存储的
redis的list底层是通过list和listnode两个结构体来实现的
listnode是一个双向链表节点,多个listnode通过prev和next指针来组成双端链表
使用list结构体来持有链表,list结构体为链表提供了表头指针head和标为指针tail,以及链表节点的数量len
2021.06.17
MySQL的索引有几种,以及其时间复杂度
全文索引、空间索引、B+ Tree索引、哈希索引
MySQL主要提供的索引是B+Tree索引和哈希索引,其时间复杂度分别是O(logN)和O(1)
InnoDB是表锁还是行锁,为什么
搜索条件有索引列的时候是行级锁,没有的话是表级锁
Go的channel(有缓冲无缓冲)
无缓冲的channel必须进出同步,不然会阻塞
有缓冲的channel可以异步,不需要生产者和消费者同步,可以给消费者一定的缓冲
用过什么消息中间件
rabbitMQ
退出程序怎么防止无缓冲channel没有消费完(channel还留有数据没有)
退出时将生产者关闭,不会产生多余的数据给消费者
go 生产者消费者
func produce(ch chan int) {defer func() {if err := recover(); err!= nil && err.(runtime.Error).Error() == "send on closed channel" {fmt.Println(err)fmt.Println("即将生产的数据:",X)} else {close(ch) // 不再继续生产}wg.Done()}()x := 0for i:= 1;i<=10;i++ {x++ch <- x}
}func consumer(ch chan int) {defer func () {if err := recover(); err != nil {fmt.Println(err)close(ch)fmt.Println(<-ch) // 触发"send on closed channel"这个错误}wg.Done()}()for x := range ch {time.Sleep(1*time.Second)fmt.Println(x)if x == 3 {panic("强制结束")}}
}func main() {fmt.Println("退出程序时,防止channel没有消费完")ch := make(chan int)wg.Add(2)go Send(ch)go Receive(ch)wg.Wait()fmt.Println("任务完成")_,ok:=<- chfmt.Println(ok)
}
手写循环队列
在这里插入代码片
golang面试题题目归纳相关推荐
- [算法] 剑指offer2 golang 面试题2:二进制加法
[算法] 剑指offer2 golang 面试题2:二进制加法 题目1: 给定两个 01 字符串 a 和 b ,请计算它们的和,并以二进制字符串的形式输出. 输入为 非空 字符串且只包含数字 1 和 ...
- cc2530期末试卷_ZigBee应用技术答案试题题目及答案,期末考试题库,章节测验答案...
ZigBee应用技术答案试题题目及答案,期末考试题库,章节测验答案 更多相关问题 [判断题] 期现套利与现货并没有实质性联系,现货风险对它们而言无关紧要.()[多选] 历史模拟法在计算VaR时具有() ...
- java面向对象编程考试题,202514HJava面向对象编程答案试题题目及答案,期末考试题库,章节测验答案...
202514HJava面向对象编程答案试题题目及答案,期末考试题库,章节测验答案 更多相关问题 导游人员在服务过程中,不得().A.向游客兜售物品 B.向游客购买物品C.接受游客给予小费 D.欺 &l ...
- Mysql高级考试题_MySQL高级应用答案试题题目及答案,期末考试题库,章节测验答案...
MySQL高级应用答案试题题目及答案,期末考试题库,章节测验答案 更多相关问题 MgSO4?7H2O在医药上常用做泻药,工业上可用氯碱工业中的一次盐泥为原料生产.已知一次盐泥中含有镁.钙.铁.铝. 下 ...
- java双语试卷_Java程序设计基础(双语)答案试题题目及答案,期末考试题库,章节测验答案...
Java程序设计基础(双语)答案试题题目及答案,期末考试题库,章节测验答案 更多相关问题 写出下列反应的化学方程式并括号中注明反应类型("化合"."分解".&q ...
- 计算机办公软件应用操作试题,计算机应用和Office办公软件考试试题题目
计算机应用和Office办公软件考试试题题目 题型 单选题 1.在选定了整个表格之后,若要删除整个表格中的内容,以下哪个操作正确( ) 选择A 单击"表格"菜单中的"删除 ...
- Interview100 经典面试题题目汇总
Interview100 经典面试题题目汇总 由于最近准备面试,找到了比较经典的面试100题进行学习一下,总结整理如下.原100道面试题链接为:https://blog.csdn.net/v_july ...
- go golang 笔试题 面试题 笔试 面试
发现go的笔试题目和面试题目还都是比较少的,于是乎就打算最近总结一下.虽然都不难,但是如果没有准备猛地遇到了还是挺容易踩坑的. 就是几个简单的笔试题目,也可能面试的时候直接给看让说结果. go面试题大 ...
- 2023版golang面试题100道(slice)
面试题合集目录 整型切片如何初始化? s1 := make([]int, 0) s2 := make([]int, 5, 10) s3 := []int{1, 2, 3} nil切片和空切片指向的地址 ...
最新文章
- 激光雷达激烈竞争市场
- dojo从asp.net中获取json数据
- cnblogs,我来了
- codeforces 785D D. Anton and School - 2
- Postfix邮件地址改写(header)
- Spark SQL之jdbc方式访问
- 以太网RJ45 接线标准 线序(备忘)
- 1346. 检查整数及其两倍数是否存在 golang
- C语言 指针声明和定义 - C语言零基础入门教程
- 我的内核学习笔记12:linux i2c-gpio驱动应用实例
- volte 是什么意思
- 营业执照psd模板2020_荣誉证书聘书奖状模板,CFR矢量素材PSD源文件,700张精美套用...
- “敏捷”联袂“ALM” 上演市场模范夫妻秀
- 支持下一代分布式应用链开发的区块链服务互联网
- CISSP 认证的 12 部优秀 CISSP 书籍和学习指南+学习网站
- 麻将游戏简介firefly游戏框架介绍
- 网卡 eth0,eth1,eth2,lo分别代表是什么 意思
- 【畅购商城】 B2B、 C2C、B2C、C2B、O2O、B2B2C电商模式介绍与分析
- 写给这批≥30岁的测试工程师 。
- 艰涩难懂,不存在的,消息队列其实很简单
热门文章
- jquery.form.js在ie8下提示下载文件
- 【论文-笔记】海防雷达仿真系统中PPI的设计与实现
- Flink Interval Join源码理解
- 基于Vue和SpringBoot的宾馆管理系统的设计和实现
- ROBO Pro的机械臂编程过程
- 魔兽世界怀旧服服务器信息,魔兽世界怀旧服服务器类型有哪些_怀旧服服务器类型介绍...
- Xilinx Spartan6中oserdes2原语输出LVDS信号驱动液晶屏波形图
- Windows Phone 应用之虾米音乐
- 绕开BAT, 长成MMP
- 一起自学SLAM算法:8.3 LOAM算法