下面这段代码输出的内容
package main
import (
"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:1关系
一个协程绑定一个线程,这种最容易实现。协程的调度都由CPU完成了,不存在N:1缺点
缺点: 协程的创建、删除和切换的代价都由CPU完成,有点略显昂贵了。 M:N关系
M个协程绑定N个线程,是N:1和1:1类型的结合,克服了以上两种模型的缺点,但实现起来最为复杂
协程跟线程是有区别的,线程由CPU调度是抢占式的,协程由用户态是协作式的,一个协程让出CPU后,才执行下一个协程
3、Go语言的协程goroutineGo为了提供更容易使用的并发方法,使用了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队列是有互斥锁进行保护的。
老调度器有几个缺点:
面对之前调度器的问题,Go设计了新的调度器
在新调度器中,除了M(thread)和G(goroutine),又引进了P(processor)
Processor,它包含了运行goroutine的资源,如果线程想运行goroutine,必须先获得P,P中包含了可运行的G队列
在Go中,线程试运行goroutine的实体,调度器的功能是把可运行的goroutine分配到工作线程上
Goroutine调度器和OS调度器时通过M结合起来的,每个M代表了一个内核线程,IS调度器负责把内核线程分配到CPU的核上执行
有关P和M的个数问题
1.P的数量:
由启动时环境变量 G O M A X P R O C S 或 者 是 由 r u n t i m e 的 方 法 G O M A X P R O C E ( ) 决 定 。 这 意 味 着 在 程 序 执 行 的 任 意 时 刻 都 只 有 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何时创建,在确定了P的最大数量n后,运行时系统会根据这个数量创建n个PM何时创建,没有足够的M来关联P并运行其中的可运行的G。比如所有的M此时都阻塞住了,而P中还有很多就绪任务,就会去寻找空闲的M,而没有空闲的,就回去创建新的M 调度器的生命周期P和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 main
import "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 goroutineG拥有栈,M根据G中的栈信息和调度信息设置运行环境M运行GG退出,再次回到M获取可运行的G,这样重复下去,知道main.main退出,runtime.main执行defer和panic处理,或调到用runtime.exit退出程序调度器的生命周期几乎占满了一个Go程序的一生,runtime.main和goroutine执行之前都是为了调度器做准备工作,runtime.main的goroutine运行,才是调度器的真正开始,知道runtime.main结束而结束
2021.06.06 go struct能不能比较select因为Go是强类型语言,所以不同类型的结构不能作比较,但是同一类型的实例值是可以比较的,实例不可以比较是因为指针类型
context包的用途select常用于goroutine的完美退出
golang的select就是监听IO *** 作,当IO *** 作发生时,触发相应的动作
每个case语句里必须是一个IO *** 作,确切的说,应该是一个面向channel的IO *** 作
client如何实现长连接Context通常被称作上下文,他是一个比较抽象的概念,其本质,是【上下上下】存在上下层的传递,上会把内容传递给下。在Go语言中,程序单元也就指的是goroutine
主协程如何等其余协程完再 *** 作server是设置超时时间,for循环遍历
slice,len,cap,共享,扩容使用channel、select、context、waitgroup
map如何顺序读取append函数,因为slice底层数据结构是由数组、len、cap组成,所以在使用append扩容时,会查看数组后面有没有连续内存块,有就在后面添加,没有就重新生成一个大的数组
实现setmap不能顺序读取,因为map是无序的,想要有序读取,首先的解决的问题就是,把key变为有序的,所以可以把key放入切片对切片进行排序,遍历切片,通过key取值
type inter interface{}
type Set struct {
m map[inter]bool
sync.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
实现消息队列(多生产者,多消费者)
大文件排序使用切片加锁可以实现
基本排序,哪些是稳定的归并排序,分而治之,拆分为小文件
HTTP GET和Head稳定的排序:
冒泡排序 o(n2)鸡尾酒排序(双向冒泡排序)o(n2)插入排序 o(n2)桶排序 o(n);需要o(k)额外计数排序 o(n+k)归并排序 o(nlogn)原地归并排序 o(n2)二叉树排序 o(nlogn)
HTTP keep-alive400 bad request 请求报文存在语法错误
401 unauthorized 表示发送的请求需要有通过HTTP认证的认证信息
403 forbidden 表示对请求资源的访问被服务器拒绝
404 not found 表示在服务器上没有找到请求的资源
HTTP能不能一次连接多次请求,不等后端返回client发出的HTTP请求头需要增加Connection:keep-alive字段
web-server端需要能识别connection:keep-alive字段,并且在http的response里指定connection:keep-alive字段,告诉client,我能提供keep-alive服务,并且”应允“client我暂时不会关闭socket链接
TCP和UDP的区别,UDP有点,适用场景HTTP本质上是使用socket链接,因此发送请求,接写入tcp缓冲,是可以多次进行的,这也是HTTP是无状态的原因
2021.06.09 time-wait的作用tcp传输的是数据流,而UDP是数据包,tcp会经过三次握手,udp不需要
数据库如何建索引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))
)
2021.06.11 死锁条件,如何避免孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。
linux命令,查看端口占用,cpu负载,内存占用,如何发送信号给一个进程死锁的条件:
系统资源不足进程运行推进的顺序不合适资源分配不当等死锁产生的四个必要条件
互斥持有并等待不可抢占环路等待如何避免:
打破互斥条件打破不可抢占条件打破占有且申请条件打破循环等待条件
2021.06.12 Slice与数组的区别,Slice底层结构查看端口占用:
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负载:
atoptopuptime查看内存占用:
topfreecat 、proc/meminfo如何发送信号给一个进程
kill
redis的基本数据结构区别
长度不可变,初始化的时候需要声明数组的长度
数组:Slice:
Slice长度不定,append时会自动扩容,属于引用类型Slice底层结构:
type slice struct {
array unsafe.Pointer
len int
cap int
}
Slice的结构由三部分组成,Pointer是一个指向数组的指针,len表示长度,cap表示容量,cap总是且必须大于len
Redis的List用过吗?底层怎么实现的?string——字符串
redis最简单的数据结构;动态字符串,采用与分配冗余空间的方法
hash——字典
hashmap,哈希
list——列表
其实就是链表;Redis 还提供了 *** 作 List 中某一段元素的 API,你可以直接查询,删除 List 中某一段的元素
set——集合
集合就是一堆不重复值的组合
sorted list——有序集合
比普通set多了一个权重score,使集合中的元素能够按照score进行有序排序
2021.06.17 MySQL的索引有几种,以及其时间复杂度没用过,但是了解过
底层是通过链表(双向和压缩)来实现的
链表与数组最大的区别就是链表是链式存储,数组是连续存储的
redis的list底层是通过list和listnode两个结构体来实现的
listnode是一个双向链表节点,多个listnode通过prev和next指针来组成双端链表
使用list结构体来持有链表,list结构体为链表提供了表头指针head和标为指针tail,以及链表节点的数量len
InnoDB是表锁还是行锁,为什么全文索引、空间索引、B+ Tree索引、哈希索引
MySQL主要提供的索引是B+Tree索引和哈希索引,其时间复杂度分别是O(logN)和O(1)
Go的channel(有缓冲无缓冲)搜索条件有索引列的时候是行级锁,没有的话是表级锁
用过什么消息中间件无缓冲的channel必须进出同步,不然会阻塞
有缓冲的channel可以异步,不需要生产者和消费者同步,可以给消费者一定的缓冲
退出程序怎么防止无缓冲channel没有消费完(channel还留有数据没有)rabbitMQ
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 := 0
for 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:=<- ch
fmt.Println(ok)
}
手写循环队列
在这里插入代码片
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)