总结

golang

1.time.Now()返回的是什么时间

eg:2021-03-31 16:25:59.153436 +0800 CST m=+0.000168605

返回带有时区的时间

2.如何做一个逻辑正确但 golang 调度器(1.10)无法正确应对,进而导致无法产生预期结果的程序。调度器如何改进可以解决此问题?

不会扩栈的死循环在触发 GC 的时候都有可能有问题。1.13 开始引入基于信号的调度。

3.进程、线程、协程、goroutine区别,windows的协程和goroutine区别

进程:既不共享堆,也不共享栈。操作系统调度。进程是系统进行资源分配和调度的一个独立单位,每个进程都有自己的独立内存空间,拥有自己独立的堆和栈,进程由操作系统调度,不同进程通过进程间通信来通信,缺点:由于进程比较重量,占据独立的内存,所以开销比较大。优点:相对比较稳定安全

线程:共享堆,不共享栈。操作系统调度。线程是进程的一个实体,它是比进程更小的能独立运行的基本单位。优点:线程间通信主要通过共享内存,上下文切换很快,资源开销较少。缺点:但相比进程不够稳定容易丢失数据

协程:共享堆,不共享栈。协程由代码显示调度。轻量级线程,协程的调度完全由用户控制。直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快

goroutine:本质上,goroutine 就是协程。不同的是,Golang 在 runtime、系统调用等多方面对goroutine 调度进行了封装和处理,当遇到长时间执行或者进行系统调用时,会主动把当前 goroutine 的CPU (P) 转让出去,让其他 goroutine 能被调度并执行,也就是 Golang 从语言层面支持了协程。Golang 的一大特色就是从语言层面原生支持协程,在函数或者方法前面加 go关键字就可创建一个协程。

资源利用最大化:一个应用程序一般对应一个进程,一个进程一般有一个主线程,还有若干个辅助线程,线程之间是平行运行的,在线程里面可以开启协程,让程序在特定的时间内运行。

其他方面比较:
1. 内存消耗方面
    每个 goroutine (协程) 默认占用内存远比 Java 、C 的线程少。
    goroutine:2KB 
    线程:8MB
2. 线程和 goroutine 切换调度开销方面
    线程/goroutine 切换开销方面,goroutine 远比线程小
    线程:涉及模式切换(从用户态切换到内核态)、16个寄存器、PC、SP...等寄存器的刷新等。
    goroutine:只有三个寄存器的值修改 - PC / SP / DX.  


4.协程go routine语言层面实现并发原理?

eg:a,b协程
	协程执行时:协程是基于线程的。内部维护了一组数据结构和n个线程,真正的执行还是线程。a,b协程代码被扔进一个待执行的队列中,由这n个线程从队列拉出来执行。这样就解决啦协程的执行问题。
	协程切换:golang对各种io函数进行了封装,这些封装的函数提供给应用程序使用,内部调用了操作系统的一步io函数,当这些异步函数返回busy或者bloking时,golang就会把现有的执行协程压栈,让线程去拉另外一个协程代码来执行

5.协程快的原因?

原因:
1.线程切换需要陷入内核,然后进行上下文切换,且切换时间点由系统内核决定。代价大
2.协程是在用户态由协程调度器来完成,不需要陷入内核,代价小
3.协程的切换点是有协程调度器来决定,不是由系统内核决定

6.GC垃圾回收机制

首先:垃圾回收的必要条件是,内存位于一致状态
线程内部处理方式:如果交给系统去做,会暂停所有的线程使其一致
go里面调度器:调度器知道什么时候内存位于一致状态,那么就没有必要暂停所有运行的协程

7.go有用过什么框架或者包

框架:gin,beego
包:http,fmt,time,rand(随机),md5,snowflake(产生有序且唯一的id),redigo(redis),
go-redis(redis),sqlx(mysql),sql(mysql),zap(log日志库)

8.go的调度、GMP怎么运行

G:代表一个go routine单元。
M:代表内核线程,所有的G都要放在M上才能运行。
P:代表控制器,调度G到M上,其维护了一个队列,存储了所有需要它来调度的G。

eg:a,b协程
	协程执行时:协程是基于线程的。内部维护了一组数据结构和n个线程,真正的执行还是线程。a,b协程代码被扔进一个待执行的队列中,由这n个线程从队列拉出来执行。这样就解决了协程的执行问题。
	协程切换:golang对各种io函数进行了封装,这些封装的函数提供给应用程序使用,内部调用了操作系统的一步io函数,当这些异步函数返回busy或者bloking时,golang就会把现有的执行协程压栈,让线程去拉另外一个协程代码来执行

9.go struct能不能比较

因为是强类型语言,所以不同类型的结构不能作比较,
但是同一类型的实例值是可以比较的
实例不可以比较,因为是指针类型

10.select可以用于什么,常用语gorotine的完美退出

select就是用来监听和channel有关的IO操作,当 IO 操作发生时,触发相应的动作。
func main() {
	var chan1 = make(chan int)
	var chan2 = make(chan int)
	select {
	case <-chan1:
		// 如果chan1成功读到数据,则进行该case处理语句
	case chan2:
		// 如果chan2成功读到数据,则进行该case处理语句
	default:
		// 如果上面都没有成功,则进行该case处理语句
	}
}

11.手撸定时器

主要两个方法:一:NewTimer	ch:=time.NewTimer(2 * time.Second)  //时间到了只响应一次,线程结束	text:=<-ch.C. //没有消息之前会阻塞func main() {	//创建一个定时器,设置时间为2s,2s后,往time通道写内容(当前时间)	timer := time.NewTimer(2 * time.Second)	fmt.Println("当前时间:", time.Now())	//2s后,往timer.C写数据,有数据后,就可以读取	t := <-timer.C //channel没有数据前后阻塞	fmt.Println("t = ", t)}二:NewTicker	ch:=time.NewTicker(1 * time.Second  //时间到了循环响应,线程不会结束  text:=<-ch.C. //没有消息之前会阻塞 func main() {	timer := time.NewTicker(1 * time.Second)	for {		<-timer.C		fmt.Println("时间到")	}}               

12.client如何实现长连接

server是设置超时时间,for循环遍历的

13.主协程如何等其余协程完再操作

1.使用channel进行通信,使用计数器,完成一个子线程计数器减一,计数器为0时,主线程退出2.var wg sync.WaitGroupwg.Add(2)  //任务数wg.Done() //子线程结束wg.Wait() //主线程等待

14.slice,len,cap,共享,扩容

	append函数,因为slice底层数据结构是,由数组、len、cap组成,所以,在使用append扩容时,会查看数组后面有没有连续内存快,有就在后面添加,没有就重新生成一个大的数组len:查看数组的长度cap:查看数组的容量eg:func main() {	li := []int{1, 2, 3}	fmt.Println(len(li)) //此时长度3	fmt.Println(cap(li)) //此时容量3	li = append(li, 4)	fmt.Println(len(li)) //此时长度4	fmt.Println(cap(li)) //此时容量6,append翻倍}

15.map如何顺序读取

	map不能顺序读取,是因为他是无序的,想要有序读取,首先的解决的问题就是,把key变为有序,所以可以把key放入切片,对切片进行排序,遍历切片,通过key取值。

16.实现消息队列(多生产者,多消费者)

package main

import (
	"fmt"
	"sync"
)

var wg sync.WaitGroup

func main() {
	wg.Add(2)
	out := producer()
	consumer(out)
	defer fmt.Println("主线程结束")
	wg.Wait() //等待
}

//此通道只能写,不能读
func producer() chan interface{} {
	ch := make(chan interface{})
	go func() {
		for i := 0; i < 5; i++ {
			ch <- fmt.Sprintf("协程1-%d", i) //写入字符串
		}
		defer close(ch)
		wg.Done() //结束
	}()
	return ch
}

//此channel只能读,不能写
func consumer(data chan interface{}) {
	defer fmt.Println("读取结束")
	go func() {
		for num := range data {
			fmt.Println(num)
		}
		wg.Done() //结束
	}()

}

17.Slice与数组区别

slice切片slice是可变长的 定义完一个slice变量之后,不需要为它的容量而担心,你随时可以往slice里面加数据 

18.消息推送

websocket,队列

19、反射reflect.DeepEqual反射

package mainimport (	"fmt"	"reflect")func main() {  // m1,m2顺序不一样,做比较	m1 := map[string]interface{}{"a": 1, "b": 2, "c": 3}	m2 := map[string]interface{}{"a": 1, "c": 3, "b": 2}	fmt.Println(reflect.DeepEqual(m1, m2))  // true}

20.用过的中间件

jwt认证中间件记录日志的中间件

21.流量控

利用go channel实现限流量控制,原理:设置一个缓冲通道,设置访问中间键,当用户请求连接时判断channel里面长度是不是大于设定的缓冲值,如果没有就存入一个值进入channel,如果大于缓冲值,channel自动阻塞。当用户请求结束的时候,取出channel里面的值。

22.限流器

限流器是后台服务中的非常重要的组件,可以用来限制请求速率,保护服务,以免服务过载。 限流器的实现方法有很多种,例如滑动窗口法、Token Bucket、Leaky Bucket等。

其实golang标准库中就自带了限流算法的实现,即golang.org/x/time/rate。 该限流器是基于Token Bucket(令牌桶)实现的。

简单来说,令牌桶就是想象有一个固定大小的桶,系统会以恒定速率向桶中放Token,桶满则暂时不放。 而用户则从桶中取Token,如果有剩余Token就可以一直取。如果没有剩余Token,则需要等到系统中被放置了Token才行。

23.http版本区别

四大特性
	1.基于请求响应
		一次请求对应一次响应 
	2.基于TCP/IP作用于应用层之上的协议
	3.无状态
		不保留客户端的状态
		无论你来多少次 我都待你入初见
		cookie   session    token  ...
	4.无连接
		长链接   websocket(类似于http协议的大补丁)  聊天室相关

版本区别

什么是HTTP和HTTPS?

HTTP是浏览器与服务器之间以明文的方式传送内容的一种互联网通信协议。

HTTPS是在HTTP的基础上主要基于SPDF协议结合SSL/TLS加密协议,客户端依靠证书验证服务器身份传递加密信息的通信协议。

1991年  HTTP/0.9 仅支持GET请求,不支持请求头

1996年  HTTP/1.0 默认短连接(一次请求建议一次TCP连接,请求完就断开),支持GET、POST、 HEAD请求

1999年  HTTP/1.1 默认长连接(一次TCP连接可以多次请求);支持PUT、DELETE、PATCH等六种请求,增加host头,支持虚拟主机;支持断点续传功能

2015年  HTTP/2.0 多路复用,降低开销(一次TCP连接可以处理多个请求);
          服务器主动推送(相关资源一个请求全部推送);
          解析基于二进制,解析错误少,更高效(HTTP/1.X解析基于文本);
          报头压缩,降低开销。

24.输入www.baidu.com发生的事情

第一步 浏览器查找该域名的 IP 地址

第二步 浏览器根据解析得到的IP地址向 web 服务器发送一个 HTTP 请求,tcp三次握手

第三步 服务器收到请求并进行处理

第四步 服务器返回一个响应

第五步 浏览器对该响应进行解码,渲染显示。

第六步 页面显示完成后,浏览器发送异步请求。

25.堆栈和内存区别

堆栈都在内存中

堆:动态,由系统运行时自动分配释放空间
栈:静态,由程序编译时分配释放,若不释放,程序结束系统回收
栈需要申请内存空间,可能申请失败

速度:栈>堆
内存大小:堆>栈

堆和栈:先进后出
队列:先进先出

26.python和go区别

1、范例:python是一种基于面向对象编程的多范式、命令式和函数式编程语言;而GO语言是一种基于并发编程范式的过程编程语言,它与C具有表面相似性。

2、类型化:python是动态类型语言,而GO是一种静态类型语言。

3、并发:Python没有提供内置的并发机制,而Go有内置的并发机制。

4、安全性:python是一种强类型语言,经过编译的,因此增加了一层安全性;GO具有分配给每个变量的类型,因此提供安全性。

5、速度:Go的速度远远超过Python。

6、用法:python更多用于web应用程序,非常适合解决数据科学问题;GO语言更多围绕系统编程,GO更像是系统语言。

7、管理内存:Go允许程序员在很大程度上管理内存;而python语言内存管理完全自动化并由python VM管理,不允许程序员对内存管理负责。

8、库:对比GO语言,python提供的库数量要大得多。

9、语法:Python的语法使用缩进来指示代码块。Go的语法基于打开和关闭括号。

10、详细程度:想要获得同样功能,Golang代码通常需要编写比Python代码更多的字符

操作系统

1.孤儿进程,僵尸进程

孤儿进程:
		在子进程没有结束时,主进程没有“正常结束”, 子进程PID不会被回收。等子进程结束,由init孤儿院回收

僵尸进程:
		一个进程创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。
		在子进程结束后,主进程没有正常结束, 子进程PID不会被回收。危害:致系统不能产生新的进程
   		- 操作系统中的PID号是有限的,如有子进程PID号无法正常回收,则会占用PID号。
        - 资源浪费。
        - 若PID号满了,则无法创建新的进程。

		在操作系统领域中,孤儿进程指的是在其父进程执行完成或被终止后仍继续运行的一类进程。这些孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

mysql

1.mysql创建索引

create index 
CREATE INDEX index_name ON table_name (column_list)


在表格上面创建某个一个唯一的索引。
CREATE UNIQUE INDEX 索引名称
ON 表名称 (列名称) 

2.储存引擎

Innodb: 5.5之后默认的存储引擎 查询速度较myisam慢 但是更安全,支持事务,支持行锁,支持外键 行锁:同一时间只能一个用户操作这行数据 2两个文件

myisam: mysql老版本用的存储引擎 5.5之前 3个文件
memory: 内存引擎(数据全部存在内存中),断电或者服务端重启之后所有数据都没有了 1个文件
blackhole: 无论存什么 都立马消失(黑洞) 1个文件

3.死锁的条件,如何避免

1. 系统资源的竞争
系统资源的竞争导致系统资源不足,以及资源分配不当,导致死锁。
2. 进程运行推进顺序不合适
进程在运行过程中,请求和释放资源的顺序不当,会导致死锁

死锁条件:
互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。

循环等待条件: 若干进程间形成首尾相接循环等待资源的关系
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。


避免死锁:
1、系统对进程发出的每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,如果分配后系统可能发生死锁,则不予分配,否则予以分配,这是一种保证系统不进入死锁状态的动态策略。 

2、银行家算法
申请的贷款额度不能超过银行现有的资金总额
分批次向银行提款,但是贷款额度不能超过一开始最大需求量的总额
暂时不能满足客户申请的资金额度时,在有限时间内给予贷款
客户要在规定的时间内还款
如果操作系统能保证所有进程在有限时间内得到需要的全部资源,则系统处于安全状态否则系统是不安全的。

4.事务

项目里的微信支付这块,在支付完微信通知这里,收到两次微信相同的支付通知,怎么防止重复消费(类似接口的幂等性),说了借助Redis或者数据库的事务

redis

1.redis的hash原理

数组+链表。哈希表是一种线性表的存储结构。哈希表由直接寻址表和一个哈希函数构成。哈希函数h(k)将元素关键字k作为自变量,返回元素的存储下标

2.什么是hash冲突,如何解决?

哈希冲突:哈希函数h(k)返回的key相同,导致两个同样的key指向不同的值
解决1:开放寻址法
	如果哈希函数返回的位置已经有值,则可以向后探查新的位置来储存这个值
	探查的方法:
		线性探查:如果位置i被占用,则探查i+1,i+2...
		二次探查:如果位置i被占用,则探查i+1平方,i-1平方,i+2平方,i-2平方...
		二度哈希:有n个哈希函数,当使用第1个哈希函数h1发生冲突时,则尝试使用h2,h3,h4...
解决2:拉链法
	哈希表的每一个位置都连接一个链表,当冲突发生时,冲突的元素将被加到该位置链表的最后。
	缺点:需要考虑实际情况,拉链如果太短,就是冲突的概率太多太多,导致每一个位置后面一长串的链表,就和链表一样了,查找的速度很底,成链状态。如下图:

img

3.什么是渐进式hash

普通hash:	我们知道当hash表满员时(或负载因子高于阈值时)会进行rehash,也就是重新调整空间大小,并拷贝原来的数据。这里rehash就是优化效率的关键。例如假设有1w个元素,rehash时要一次性拷贝1w元素到新的空间,这样势必会成为很大的负担,庞大的计算量可能会导致服务器在一段时间内停止服务。
渐进式hash:为了避免 rehash 对服务器性能造成影响, 服务器不是一次性将 ht[0] 里面的所有键值对全部 rehash 到 ht[1] , 而是分多次、渐进式地将 ht[0] 里面的键值对慢慢地 rehash 到 ht[1] 。

4.zset原理,跳表查询,skiplist

zset原理:有序集合,上层单链表,底层双链表。
跳表查询:跳表可以当做优先队列

跳表查询,skiplist

在这样一个链表中,如果我们要查找某个数据,那么需要从头开始逐个进行比较,直到找到包含数据的那个节点,或者找到第一个比给定数据大的节点为止(没找到)。也就是说,时间复杂度为O(n)。同样,当我们要插入新数据的时候,也要经历同样的查找过程,从而确定插入位置。

假如我们每相邻两个节点增加一个指针,让指针指向下下个节点,如下图:

这样所有新增加的指针连成了一个新的链表,但它包含的节点个数只有原来的一半,(上图中是7, 19, 26),现在当我们想查找数据的时候,可以先沿着这个新链表进行查找。当碰到比待查数据大的节点时,再回到原来的链表中进行查找。比如,我们想查找23,查找的路径是沿着下图中标红的指针所指向的方向进行的:见下图

查找23:

23首先和7比较,再和19比较,比它们都大,继续向后比较。
但23和26比较的时候,比26要小,因此回到下面的链表(原链表),与22比较。
23比22要大,沿下面的指针继续向后和26比较。23比26小,说明待查数据23在原链表中不存在,而且它的插入位置应该在22和26之间。

在这个查找过程中,由于新增加的指针,我们不再需要与链表中每个节点逐个进行比较了。需要比较的节点数大概只有原来的一半。

利用同样的方式,我们可以在上层新产生的链表上,继续为每相邻的两个节点增加一个指针,从而产生第三层链表。如下图:

在这个新的三层链表结构上,如果我们还是查找23,那么沿着最上层链表首先要比较的是19,发现23比19大,接下来我们就知道只需要到19的后面去继续查找,从而一下子跳过了19前面的所有节点。可以想象,当链表足够长的时候,这种多层链表的查找方式能让我们跳过很多下层节点,大大加快查找的速度。

posted @ 2021-04-29 11:56  Jeff的技术栈  阅读(155)  评论(0编辑  收藏  举报
回顶部