【Golang】Go语言面试①

Go 语言笔试面试题汇总

Go 语言笔试面试题(基础语法)

Golang:内存分配和垃圾回收

文章目录

一.showmebug面试题

为什么协程比线程轻量?

  • go协程调用跟切换比线程效率高
    进程 系统进行资源分配的基本单位,有独立的内存空间
    线程 cpu调度和分派的基本单位,线程依赖进程,共享父进程的 资源
    协程 是用户态 的 轻量级线程,协程的调度完全由用户控制 。协程 间切换只需要保存上下文,没有内核的开销

(线程并发执行流程:
  线程是内核对外提供的服务,应用程序可以通过系统调用让内核启动线程,由内核来负责线程调度和切换。线程在等待IO操作时线程变为unrunnable状态会触发上下文切换。现代操作系统一般都采用抢占式调度,上下文切换一般发生在 时钟中断和系统调用返回前,调度器计算当前线程的时间片,如果需要切换就从运行队列中选出一个目标线程,保存当前线程的环境,并且恢复目标线程的运行环境,最典型的就是切换ESP指向目标线程内核堆栈,将EIP指向目标线程上次被调度出时的指令地址。)

==go协程并发执行流程:不依赖操作系统和其提供的线程,golang自己实现的CSP并发模型实现:M, P, G

  go协程也叫用户态线程,协程之间的切换发生在用户态。协程 间切换只需要保存上下文。 在用户态没有时钟中断,系统调用等机制,因此效率高==
  • 协程非常轻量
    执行go协程只需要极少的栈内存(大概是4~5KB),默认情况下,线程栈的大小为1MB。

    上下文切换代价小 上下文且含涉及到3个寄存器,线程切换涉及到15个寄存器

  • GPM
    G保存运行堆栈,执行函数。可重用。goroutine就是一段代码,一个函数入口,以及在堆上为其分配的一个堆栈。所以它非常廉价,我们可以很轻松的创建上万个goroutine,但它们并不是被操作系统所调度执行。

协程阻塞

1 原子 互斥量 和通道操作导致的 阻塞,会被切出去,然后会去执行 LRQ的其他G
2 网络请求和io导致的 阻塞。会被切到网络轮训器等待异步结果。P处理其他的G。异步结果拿到后,重新加入P。不需要 额外的M
3 系统函数操作导致的阻塞,比如文件io,P会切换到新的M去执行剩余的任务,阻塞的系统调用完成后,G重新加入P
4 后台 执行sleep导致的阻塞。golong后台监视线程sysmon,会把它标记为可以强占,LRQ优先执行。等到这个G继续执行的时候,就会被强占,同时保护现场,重新加入P的队列等待下次执行

通过GPM实现少量线程支持大量的并发,并通过网络轮训器 和sysmon来减少线程的阻塞。充分利用 有限的线程资源 ,提高go的运行效率

https://blog.csdn.net/u011957758/article/details/83018425

四、性能优化

  • 性能查看工具 pprof,trace 及压测工具 wrk 或其他压测工具的使用要比较了解。
  • 代码逻辑层面的走读非常重要,要尽量避免无效逻辑。
  • 对于 golang 自身库存在缺陷的,可以寻找第三方库或自己改造。
  • golang 版本尽量更新,这次的测试是在 golang1.12 下进行的。而 go1.13 甚至 go1.14 在很多地方进行了改进。比如 fmt.Sprintf,sync.Pool 等。替换成新版本应该能进一步提升性能。
  • 本地 benchmark 结果不等于线上运行结果。尤其是在使用缓存来提高处理速度时,要考虑 GC 的影响。
  • 传参数或返回值时,尽量按 golang 的设计哲学,少用指针,多用值对象,避免引起过多的变量逃逸,导致 GC 耗时暴涨。struct 的大小一般在 2K 以下的拷贝传值,比使用指针要快(可针对不同的机器压测,判断各自的阈值)。
  • 值类型在满足需要的情况下,越小越好。能用 int8,就不要用 int64。
  • 资源尽量复用,在 golang1.13 以上,可以考虑使用 sync.Pool 缓存会重复申请的内存或对象。或者自己使用并管理大块内存,用来存储小对象,避免 GC 影响(如本地缓存的场景)。

go

go server

go 自带的web server性能非常强悍,主要就是因为使用了协程,对于每一个web请求,服务器都会新开一个go协程去处理,一个服务器可以轻松同时开启上万个协程

在这里插入图片描述

在这里插入图片描述

4. 项目

gin框架

Gin 路由解析

路由是web框架的核心功能。通常路由实现是这样的:根据路由里的 / 把路由切分成多个字符串数组,然后按照相同的前子数组把路由构造成树的结构;寻址时,先把请求的 url 按照 / 切分,然后遍历树进行寻址。

比如:定义了两个路由 /user/get,/user/delete,则会构造出拥有三个节点的路由树,根节点是 user,两个子节点分别是 get delete。

上述是一种实现路由树的方式,且比较直观,容易理解。对 url 进行切分、比较,时间复杂度是 O(2n)。

Gin的路由实现使用了类似前缀树的数据结构,只需遍历一遍字符串即可,时间复杂度为O(n)。

在这里插入图片描述

通过root -> child 的形式,形成一个路由解析树。当请求进来时,如果能匹配上一条线,则进入相应的逻辑了;如果没有匹配的,直接404。

每个线程/协程占用多少内存知道吗?

goroutine 所占用的内存,均在栈中进行管理 goroutine 所占用的栈空间大小,由 runtime 按需进行分配 以
64位环境的 JVM 为例,会默认固定为每个线程分配 1MB 栈空间,如果大小分配不当,便会出现栈溢出的问题

go协程   协程(coroutine)是Go语言中的轻量级线程实现,由Go运行时(runtime)管理。

进程、线程、协程的关系和区别:

进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。

线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。

协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。

如果若干线程中一个线程OOM,会发生什么?如果是Goroutine 呢? 项目 中出现过OOM吗?

达到阈值会被kill掉

goroutine 内存爆,性能分析,抓火焰图,内存分布图去看,

http://liumurong.org/2019/12/gin_pprof/

追踪bug

线上链路追踪

错误处理

error往外抛,日志记录,

try,except 中间件记录?社区不推荐

很多if err !=nil 如何优化 ?
一行代码 三行 if err !=nil

姿势一:失败的原因只有一个时,不使用error
姿势二:没有失败时,不使用error
姿势三:error应放在返回值类型列表的最后
姿势四:错误值统一定义,而不是跟着感觉走
姿势五:错误逐层传递时,层层都加日志

错误值统一定义, error信息,客户端、定位错误等

https://www.sohu.com/a/342949702_657921

https://www.cnblogs.com/zhangboyu/p/7911190.html

如果若干Goroutine有一个panic,会发生干什么?

Goroutine里面看有没有panic捕获,如果没做,整个程序都会crush掉

goroutine发生panic,只有自身能够recover,其它goroutine是抓不到的,这是常识啊。

一个defer能捕获到一个Goroutine下 子Goroutine下的panic吗?

不行,子Goroutine下做recovery捕获

gRPC gateway

gRPC gateway库,可以添加proto文件,添加描述信息,可以生成http访问方式的

proto 文件怎么管理

monorepo 全部放在一起

服务发现是怎么做的

etcd启动时 去服务地址 注册服务信息,来监听它的信息,来获取到他的每一个server地址,再用grpc load balance做负载均衡

注册中心挂了怎么办

五节点, raft算法保证一致性

gin 怎么做参数校验

类型,一些限制

package requests

import (
	"net/http"
	"sandan/application/utils"
	"time"

	"github.com/gin-gonic/gin"
	"gopkg.in/go-playground/validator.v9"
)

type ImMessageRequest struct {
	Mode     int `form:"mode" validate:"omitempty,min=0,max=10"`
}

func ImMessageVerify() gin.HandlerFunc {
	return func(c *gin.Context) {
		var data ImMessageRequest
		//绑定数据
		errA := c.ShouldBind(&data)
		//校验请求数据
		validate := validator.New()
		//自己定义tag标签以及与之对应的处理逻辑
		errB := validate.Struct(&data)
		if errA != nil || errB != nil {
			c.JSON(http.StatusOK, gin.H{
				"errcode":   utils.ERROR_VALIDATOR,
				"errmsg":    utils.GetMsg(utils.ERROR_VALIDATOR),
				"timestamp": time.Now().UnixNano() / 1e6,
			})
			//终止
			c.Abort()
			return
		} else {
			//该句可以省略,写出来只是表明可以进行验证下一步中间件,不写,也是内置会继续访问下一个中间件的
			c.Next()
		}

	}
}

绑定时 调用函数

import (
	"encoding/json"
	"github.com/asaskevich/govalidator"
	"gopkg.in/go-playground/validator.v9"
	"regexp"
)

type DomainCheckReqest struct {
	Limit int `form:"limit" binding:"required" validate:"required,min=1,max=100"`
	Url string `form:"url" validate:"required,CheckUrl"`
}

//第三方网站访问记录
func DomainCheckVerify() gin.HandlerFunc {
	return func(c *gin.Context) {
		var data DomainCheckReqest
		//绑定数据
		errA := c.ShouldBind(&data)
		//校验请求数据
		validate := validator.New()
		//自己定义tag标签以及与之对应的处理逻辑
		validate.RegisterValidation("CheckUrl", CheckUrl)
		errB := validate.Struct(&data)
		if errA != nil || errB != nil {
			c.JSON(http.StatusOK, gin.H{
				"errcode":   utils.ERROR_VALIDATOR,
				"errmsg":    utils.GetMsg(utils.ERROR_VALIDATOR),
				"timestamp": time.Now().UnixNano() / 1e6,
			})
			//终止
			c.Abort()
			return
		} else {
			//该句可以省略,写出来只是表明可以进行验证下一步中间件,不写,也是内置会继续访问下一个中间件的
			c.Next()
		}

	}
}
//判断url
func CheckUrl(fl validator.FieldLevel) bool {
	url := fl.Field().String()
	ok := govalidator.IsURL(url)
	return ok

}
中间件

日志打印、限流、链路追踪开启,类似于node 剥洋葱模型

go 解析tag用的什么, 反射的原理

反射,reflect可以获取原数据, 获取里面的tag,对应的描述

原理:reflect包,通过获取的tag, value获取更详细的信息,方法等

反射:通过字符串来调用函数

在这里插入图片描述

golang锁

锁的几种模式,正常、饥饿模式其实是Mutex模式,并不是pmg模型,深入了解?区别?

map协程不安全,加读写锁,防止并发去修改,现在用的更多的是sync.map(协程安全)

channel有用过吗,哪些需要注意的地方✅

并发控制,select 选择,goroutine传递值等等

1.不能在close之后发送信息, 但仍然可以从channel读取数据

package main

import "fmt"

func main(){
	var ch = make(chan int,10)

	ch <- 1
	ch <- 2
	close(ch)
	//上面已经将管道关闭,此处不能再像管道添加数据
	//ch <- 6

	//管道的关闭不影响从管道读取数据
	n1 := <- ch
	n2 := <- ch
	fmt.Println(n1,n2)
}

官方推荐 不要用共享内存来操作变量
2.

  1. chan 可以声明为只读或者只写 默认是读写都可用
  2. 使用chan完成后记得注意关闭,不关闭阻塞会导致deadlock 如果太麻烦可以使用select
  3. 使用recover, 解决协程中出现panic,导致程序崩溃
  4. 如果chan容量满了还在增加元素,或者chan没有元素还在获取元素,或者没关闭chan就遍历元素都是会报deadlock的.

四、数据库篇

数据库锁

mysql: 行锁、表锁:

使用的时候注意一点,不要使行锁退化成表锁

redis:
setnx key占用, 抢锁没问题,释放锁时过期了,delete别人的锁,redlock 里面有锁的vlaue值,删除时会判断是否一致,有重试次数,间隔

负载均衡算法

随机,轮训等
在这里插入图片描述

mutex的模式

正常、饥饿
https://blog.csdn.net/csdniter/article/details/110934260

egg-tech

参数函数

f func()为指针

在这里插入图片描述

go 切片扩容

在这里插入图片描述

// src/runtime/slice.go
// go version 1.13
func growslice(et *_type, old slice, cap int) slice {
// ...省略部分
    newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {
        newcap = cap
    } else {
        //原切片长度低于1024时直接翻倍
        if old.len < 1024 {
            newcap = doublecap
        } else {
            // Check 0 < newcap to detect overflow
            // and prevent an infinite loop.
            //原切片长度大于等于1024时,每次只增加25%,直到满足需要的容量
            for 0 < newcap && newcap < cap {
                newcap += newcap / 4
            }
            // Set newcap to the requested cap when
            // the newcap calculation overflowed.
            if newcap <= 0 {
                newcap = cap
            }
        }
    }
// ...省略部分
}

string是两部分组成的,一部分是指针+长度,另外一部分是utf-8编码的字节数组。64位下16字节

在这里插入图片描述

在这里插入图片描述

新浪面试

TCP和UDP的区别

TCP协议定义(Transimission Control Protocol)是以一种面向连接的、可靠的、基于字节流的传输层通信协议。

TCP的优点: 可靠,稳定 TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。

TCP的缺点: 慢,效率低,占用系统资源高,易被攻击 TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。 而且,因为TCP有确认机制、三次握手机制,这些也导致TCP容易被人利用,实现DOS、DDOS、CC等攻击。

UDP的优点: 快,比TCP稍安全 UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。但UDP也是无法避免攻击的,比如:UDP Flood攻击……

UDP的缺点: 不可靠,不稳定 因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。 基于上面的优缺点,

  • 小结TCP与UDP的区别:

    1.基于连接与无连接;
    2.对系统资源的要求(TCP较多,UDP少);
    3.UDP程序结构较简单;
    4.流模式与数据报模式 ;
    5.TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。

TCP与UDP区别总结:

1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接

2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付

3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的

UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)

4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信

5、TCP首部开销20字节;UDP的首部开销小,只有8个字节
6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

tcp丢包问题

二、TCP协议丢包后,如何解决丢包的问题

为了满足TCP协议不丢包。TCP协议有如下规定:

  1. 数据分片:发送端对数据进行分片,接受端要对数据进行重组,由TCP确定分片的大小并控制分片和重组

  2. 到达确认:接收端接收到分片数据时,根据分片数据序号向发送端发送一个确认

  3. 超时重发:发送方在发送分片时设置超时定时器,如果在定时器超时之后没有收到相应的确认,重发分片数据

  4. 滑动窗口:TCP连接的每一方的接受缓冲空间大小固定,接收端只允许另一端发送接收端缓冲区所能接纳的数据,TCP在滑动窗口的基础上提供流量控制,防止较快主机致使较慢主机的缓冲区溢出

  5. 失序处理:作为IP数据报来传输的TCP分片到达时可能会失序,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层;

  6. 重复处理:作为IP数据报来传输的TCP分片会发生重复,TCP的接收端必须丢弃重复的数据;

  7. 数据校验:TCP将保持它首部和数据的检验和,这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到分片的检验或有差错,TCP将丢弃这个分片,并不确认收到此报文段导致对端超时并重发

https://www.cnblogs.com/williamjie/p/9390164.html

四.图**视

4.1 1面技术面(问技术实现,底层偏多)

1.mysql 相关

B+树的优点在于:
1.由于B+树在内部节点上不包含数据信息,因此在内存页中能够存放更多的key。
数据存放的更加紧密,具有更好的空间局部性。
因此访问叶子节点上关联的数据也具有更好的缓存命中率。
2.B+树的叶子结点都是相链的,因此对整棵树的便利只需要一次线性遍历叶子结点即可。
而且由于数据顺序排列并且相连,所以便于区间查找和搜索。
而B树则需要进行每一层的递归遍历,相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。

但是B树也有优点,其优点在于:
由于B树的每一个节点都包含key和value,因此经常访问的元素可能离根节点更近,因此访问也更迅速。

回表

普通索引查找过程第二步

  • 回表查询

先通过普通索引的值定位聚簇索引值,再通过聚簇索引的值定位行记录数据,需要扫描两次索引B+树,它的性能较扫一遍索引树更低。

  • 索引覆盖

只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快。

例如:select id,age from user where age = 10;

如何实现覆盖索引

常见的方法是:将被查询的字段,建立到联合索引里去。

http://www.cppcns.com/shujuku/mysql/300346.html

多个单列索引和联合索引的区别

多个单列索引在多条件查询时只会生效第一个索引!所以多条件联合查询时最好建联合索引!

数据库其他知识点:

1、需要加索引的字段,要在where条件中
2、数据量少的字段不需要加索引;因为建索引有一定开销,如果数据量小则没必要建索引(速度反而慢)
3、如果where条件中是OR关系,加索引不起作用
4、联合索引比对每个列分别建索引更有优势,因为索引建立得越多就越占磁盘空间,在更新数据的时候速度会更慢。另外建立多列索引时,顺序也是需要注意的,应该将严格的索引放在前面,这样筛选的力度会更大,效率更高。

事务

悲观锁 乐观锁

乐观锁
总是假设最好的情况,每次去读数据的时候都认为别人不会修改,所以不会上锁, 但是在更新的时候会判断一下在此期间有没有其他线程更新该数据, 可以使用版本号机制和CAS算法实现。 乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。 在Java中java.util.concurrent.atomic包下面的原子变量类就是基于CAS实现的乐观锁。

悲观锁
总是假设最坏的情况,每次去读数据的时候都认为别人会修改,所以每次在读数据的时候都会上锁, 这样别人想读取数据就会阻塞直到它获取锁 (共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。 传统的关系型数据库里边就用到了很多悲观锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。 Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

使用场景
乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。

乐观锁适用于读比较少的情况下(多写场景),如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

乐观锁的缺点

  • 1.ABA问题

如果一个变量初次读取的时候是 A 值,它的值被改成了 B,后来又被改回为 A,那 CAS 操作就会误认为它从来没有被改变过。

J.U.C 包提供了一个带有标记的原子引用类 AtomicStampedReference 来解决这个问题, 它可以通过控制变量值的版本来保证 CAS 的正确性。 大部分情况下 ABA 问题不会影响程序并发的正确性, 如果需要解决 ABA 问题,改用传统的互斥同步可能会比原子类更高效。

  • 2.自旋时间长开销大

自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用, 第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源, 延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。 第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation) 而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

  • 3.只能保证一个共享变量的原子操作

CAS只对单个共享变量有效,当操作涉及跨多个共享变量时CAS无效。 但是从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性, 可以把多个变量封装成对象里来进行 CAS 操作. 所以我们可以使用锁或者利用AtomicReference类把多个共享变量封装成一个共享变量来操作。

项目为什么使用MQ(应用场景)

  • 削峰填谷:
    诸如秒杀、抢红包、企业开门红等大型活动时皆会带来较高的流量脉冲,或因没做相应的保护而导致系统超负荷甚至崩溃,或因限制太过导致请求大量失败而影响用户体验,消息队列 MQ 可提供削峰填谷的服务来解决该问题。
  • 异步解耦:
    交易系统作为淘宝/天猫主站最核心的系统,每笔交易订单数据的产生会引起几百个下游业务系统的关注,包括物流、购物车、积分、流计算分析等等,整体业务系统庞大而且复杂,消息队列 MQ 可实现异步通信和应用解耦,确保主站业务的连续性。
  • 顺序收发:细数日常中需要保证顺序的应用场景非常多,比如证券交易过程时间优先原则,交易系统中的订单创建、支付、退款等流程,航班中的旅客登机消息处理等等。与先进先出(First In First Out,缩写 FIFO)原理类似,消息队列 MQ 提供的顺序消息即保证消息 FIFO。
  • 分布式事务一致性:交易系统、支付红包等场景需要确保数据的最终一致性,大量引入消息队列 MQ 的分布式事务,既可以实现系统之间的解耦,又可以保证最终的数据一致性。
  • 大数据分析:数据在“流动”中产生价值,传统数据分析大多是基于批量计算模型,而无法做到实时的数据分析,利用阿里云消息队列 MQ 与流式计算引擎相结合,可以很方便的实现将业务数据进行实时分析。
  • 分布式缓存同步:天猫双 11 大促,各个分会场琳琅满目的商品需要实时感知价格变化,大量并发访问数据库导致会场页面响应时间长,集中式缓存因为带宽瓶颈限制商品变更的访问流量,通过消息队列 MQ 构建分布式缓存,实时通知商品数据的变化。

4.2 技术面2面(问技术实现较多)

http // https 区别
简历项目
问一些公司基本的情况

4.3 面技术面(主要对简历)

3.aes加密
PostgreSQL 与 MySQL比较:

5. blowfire

两次技术面,一次hr面

5.1 技术1面 项目负责人leader

5.2 2面 、技术负责人

负责人leader: 主要聊经历 技术实现等,es数据量大时成本问题等等

技术负责人: 主要聊简历,并出了一道算法题(消费者与生产者数据产生消费不同步 就报panic问题)


array [12343456321] # 是一个环型1024大小数组# ,生产者扔数据, 消费者取数据,  消费者完成了之后可以,从0处继续消费
class():
	p_index = 0
	c_index = 0
	
	p_round= 0  ## 生产者周期
	c_round= 0  ## 消费者周期

	def rpush()
	def rpop()

比如:消费者一直消费数据,生产者跟不上 的判断条件, 在c_index走到最右边时(重新去队列中拿数据),这时已无数据可拿

(c.round > p.round) and (c_index < p_index)

消费者比不上生产者

(c.round < p.round) and (c_index < p_index)

排序算法
求最n位数组的 最大子数组的最大和
import random
def max_violent(arr):
    max=-100
    a=0
    b=0
    for i in range(len(arr)):               #每次所取子数组的头
        for j in range(i,len(arr)):       #每次所取子数组的尾
            tar=0
            for k in range(i,j+1):          #累加该数组元素
                tar=tar+arr[k]
            if tar>max:
                max=tar
                a=i
                b=k
    return (max,a,b)
arr=[]
for i in range(10):
    arr.append(random.randint(-10,10))
print(arr)
print('暴力方法:',max_violent(arr))
def sub_lists(my_list):
    subs = [[]]
    for i in range(len(my_list)):
        n = i+1
        while n <= len(my_list):
            sub = my_list[i:n]
            subs.append(sub)
            n += 1

    return subs

六. flow

1.字符串转int
2. ‘’ 为byte

3. go语言关于值类型和引用类型

1、在go语言中,值类型和引用类型有以下特点:
a、值类型:基本数据类型,int,float,bool,string,以及数组和struct
特点:变量直接存储值,内存通常在栈中分配,栈在函数调用完会被释放

b、引用类型:指针,slice,map,chan等都是引用类型
特点:变量存储的是一个地址,这个地址存储最终的值。内存通常在堆上分配,通过GC回收。

4.Go语言参数传递是传值还是传引用

4.1 什么是传值(值传递, map, chan)

传值的意思是:函数传递的总是原来这个东西的一个副本,一副拷贝。比如我们传递一个int类型的参数,传递的其实是这个参数的一个副本;传递一个指针类型的参数,其实传递的是这个该指针的一份拷贝,而不是这个指针指向的值。

任何存放在内存里的东西都有自己的地址,指针也不例外,它虽然指向别的数据,但是也有存放该指针的内存。

func main() {
	i:=10
	ip:=&i
	fmt.Printf("原始指针的内存地址是:%p\n",&ip)
	modify(ip)
	fmt.Println("int值被修改了,新值为:",i)
}

 func modify(ip *int){
	 fmt.Printf("函数里接收到的指针的内存地址是:%p\n",&ip)
 	*ip=1
 }
4.2 什么是传引用(引用传递)

Go语言(Golang)是没有引用传递的,这里我不能使用Go举例子,但是可以通过说明描述。

以上面的例子为例,如果在modify函数里打印出来的内存地址是不变的,也是0xc42000c028,那么就是引用传递。

所以在这里,Go语言通过make函数,字面量的包装,为我们省去了指针的操作,让我们可以更容易的使用map。这里的map可以理解为引用类型,但是记住引用类型不是传引用。

指针类型可以修改,非指针类型不行
如struct ,传地址才能修改,make创建的map、chan,可以直接修改

4.3 特殊的slice

所以我们通过%p打印的slice变量ages的地址其实就是内部存储数组元素的地址,slice是一种结构体+元素指针的混合类型,通过元素array(Data)的指针,可以达到修改slice里存储元素的目的。

如:p:=Slice{cap:1,len:1,data:&i}

所以修改类型的内容的办法有很多种,类型本身作为指针可以,类型里有指针类型的字段也可以。

p:=Slice{cap:1,len:1,data:&i} 可以修改data字段

单纯的从slice这个结构体看,我们可以通过modify修改存储元素的内容,但是永远修改不了len和cap,因为他们只是一个拷贝,如果要修改,那就要传递*slice作为参数才可以。

https://www.flysnow.org/2018/02/24/golang-function-parameters-passed-by-value.html

random

waitgroup 如何控制并发

wait() 的时候 使用 CAS( Compare And Swap )源子操作

数据结构:

type WaitGroup struct {
	noCopy noCopy
	state1 [12]byte
	sema   uint32
}

实现原理
肯定是离不开阻塞唤醒机制和次数加减。而且是并发环境,那么次数加减要CAS。最后记录下阻塞的goroutine个数,因为要把挨个他们唤醒。

在这里插入图片描述

为什么要有CAS:因为通过锁实现原子操作时,其他线程必须等待已经获得锁的线程运行完以后才能获得资源,这样就会占用系统的大量资源

singleflight就是这样的例子。它解决了一堆人等一个人干完活的问题。就比如现在有100个线程同时请求数据库中同一行数。但只能有一个线程能读库,其他线程都阻塞等待它的结果。

https://zhuanlan.zhihu.com/p/75441551

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值