golang学习笔记-基础篇(持续更新……)

1 零碎知识点

1.1 本地doc文档

在终端执行godoc -http=:6060将本地的godoc文档发布到端口6060上,即可访问本地的godoc文档。

1.2 捕获dump信息

利用"runtime/debug"的包中stack函数捕获,将捕获的信心持久化到dump文件中:

func dump() {
	rcv_err := recover()
	if rcv_err == nil {
		return
	}
	now := time.Now()
	strTime := now.Format("20060102150405")
	name := fmt.Sprintf("dump-%s.log", strTime)
	fmt.Println("dump file in", name)
	f, err := os.Create(name)
	if err != nil {
		return
	}
	defer f.Close()
	f.WriteString(fmt.Sprintf("panic:\r\n%v", rcv_err))
	f.WriteString("\r\n\nstack:\r\n")
	f.WriteString(string(debug.Stack()))
}

1.3 "os/exec"启动进程

exec包CommandContext启动进程。

var cmd *exec.Cmd
ctx, _ := context.WithCancel(context.Background())
if len(serviceArgs) == 0 {
	cmd = exec.CommandContext(ctx, servicePath)
} else {
	cmd = exec.CommandContext(ctx, servicePath, serviceArgs)
}
var err error
go func() {
	defer cmd.Wait()
	if err = cmd.Start(); err != nil {
		return
	}
}()

需要注意的是CommandContext的第一个参数不能为WithTimeout之类的,这种情况下启动进程会在到时间后直接退出,必须用WithCancel(Backgroud)的形式.
Run是阻塞的形式
Start()非阻塞模型,需要用wait等待,一般情况下启动进程肯定是需要被启动进程一直运行的,这种情况下wait会阻塞住,大部分情况用go func(){}的形式启动进程。

1.4 观察者模式

当一种对象变化时通知另一个对象变化得到事件。定义了2个接口:观察者接口和备观察者接口。除此之外又定义了一个通知的时间结构体event,可以在这个event中增加事件的更多参数。

type event struct {
	uuid string
}
1.4.1 观察者
1.4.1.1 接口定义

定义一个函数,用于接受被观察者的事件通知

// 被观察者接口
type observer interface {
	Update(*event)
}
1.4.1.2 观察者实例

观察者实现了接口函数Update用于观察通知事件;又定一个一个辅助函数,addJob

type task struct {
	jobs []job
}

func (t *task) Update(e *event) {
	log.Printf("更新事件uuid=%v",e.uuid)
}

func (t *task) addJob(j job) {
	t.jobs = append(t.jobs, j)
}
1.4.2 被观察者
1.4.1.1 接口定义

定义三个函数,给当前被观察者增加一个观察者,删除当前被观察者的观察者和通知观察者变更事件。

// 被观察者接口
type subject interface {
	Attach(...observer) // 添加观察者
	Detach(observer)    // 删除观察者
	Notify(string)      // 通知观察者
}
1.4.1.2 被观察者实例
type job struct {
	observers []observer // 观察者对象
}

func (j *job) Attach(obs ...observer) {
	j.observers = append(j.observers, obs...)

}
func (j *job) Detach(observer) {

}
func (j *job) Notify(uuid string) {
	for _,obs:=range j.observers {
		obs.Update(event{"123"})
	}
}
1.4.3 主程序调用

定义了一个task观察者对象和job被观察者对象,当job变更时通知task对象。

package main

import "log"
func main() {
	t := new(task)

	j := job{}
	log.Printf("0-------[%+v]", j)
	j.Attach(t)
	log.Printf("1-------[%+v]", j)

	t.addJob(j)
	log.Printf("2-------[%+v]", t)
}

1.5 go程序的版本

编译的go程序执行./demo -v时打印当前的版本

package main

import (
	"fmt"
	"log"
	"os"
	"runtime"
	"strings"
)

var (
	version    = ""
	githash    = ""
	buildstamp = ""
	goversion  = fmt.Sprintf("%s %s/%s", runtime.Version(), runtime.GOOS, runtime.GOARCH)
)

func Version() {
	args := os.Args
	if len(args) == 2 {

		arg := strings.ToLower(args[1])
		if arg == "--version" || arg == "-v" {
			fmt.Printf("version: %s\n", version)
			fmt.Printf("Git Commit Hash: %s\n", githash)
			fmt.Printf("UTC+8 Build Time : %s\n", buildstamp)
			fmt.Printf("Golang Version : %s\n", goversion)
			os.Exit(0)
		}
	}
}

func main() {
	Version()
	log.Printf("hello world")
}

Makefile的实现,当直接make时,先执行go test运行所有的test用例,然后执行go build编译文件。编译后的程序版本信息中默认了version=0.0.1,date为编译的时间,gitbash为git log的commit数字,还有go的编译版本。
执行make build 时只编译,不运行编译的测试用例。手动加版本为1.0.0-stable时运行make build version=1.0.0-stable,编译的程序就有了对应的版本号。

# Go parameters
GOCMD=go
GOBUILD=$(GOCMD) build
GOCLEAN=$(GOCMD) clean
GOTEST=$(GOCMD) test
GOGET=$(GOCMD) get
BINARY_NAME=demo

# version infos
gitbash:=$(shell git rev-parse HEAD)
version:=0.0.1
date:=$(shell date "+%G%m%d%H%M%S")

# compile flags,default can not use gdb debug
flags:=-w -s
DFLAGS:=
ifeq ($(DFLAGS),debug)
	flags=
endif


all:test build

build:
	$(GOBUILD) -v -ldflags '-X "main.version=${version}" -X "main.buildstamp=${date}" -X "main.githash=${gitbash}" ${flags}' -o ${BINARY_NAME}
	
test:
	$(GOTEST) -v ./...

clean:
	$(GOCLEAN)
	rm -f $(BINARY_NAME)

run:
	$(GOBUILD) -o $(BINARY_NAME) -v ./...
	./$(BINARY_NAME)

help:
	@echo "************* help *************"
	@echo "* make      : make test and make build for all services	"
	@echo "* make test : make test for all services					"
	@echo "* make build: make build all services,if there are errors during compilation, look for the error message in the file builderr.txt"
	@echo "* make clean: remove all executable programs and intermediate temporary files generated by the compilation "

deps:
	$(GOGET) github.com/xxx/xx
	$(GOGET) github.com/xxx/xx

.PHONY: all build test clean help

make build DFLAGS="debug"编译为debug版本,可以gdb调试。

1.6 压缩go的可执行程序大小

默认的情况下编译出来的go可执行程序会非常大,动辄就是几十兆起,可以通过一些策略来尽可能的减小文件的大小。代码示例:

package main
import "fmt"
func main(){
	fmt.Println("hello world")
}
1.6.0 直接编译文件
go build -o main main.go

编译后文件大小:2068291字节

1.6.1 编译参数
1 编译参数

编译时增加-ldflags中的-s和-w参数,会去掉“符号表”“ DWARF generation”,生成的文件会减小一半左右,编译:

go build -ldflags '-s -w' -o main.release main.go

编译后:1458176字节,压缩率:(2068291-1458176)/2068291*100=29.46%

查看-ldflags参数的详细信息可以通过go tool link查看,以下为部分参数:
在这里插入图片描述

2 strip删除符号变和节

这种方式其实和linux自带的命令strip效果一致,作用是:从文件中删除符号和节。以下是参数:
在这里插入图片描述如果对于一个直接编译的go可执行程序,使用这个命令处理后减小的文件大小和增加编译项的大小差不多一致:

strip main -p -o main.strip

以上将源文件mian去掉符号和节后文件名为main.strip,执行后的文件:1455544字节,压缩率:(2068291-1455544)/2068291*100=29.62%

这两种任选其一即可,一般我们使用***编译参数控制***,即可以生成可以debug的,也可以生成release的版本

1.6.2 文件加壳压缩

在ubuntu和centos下安装upx
ubuntu下如果使用国内的源的话可以直接安装:sudo apt install upx
centos下安装:

wget -c http://ftp.tu-chemnitz.de/pub/linux/dag/redhat/el7/en/x86_64/rpmforge/RPMS/ucl-1.03-2.el7.rf.x86_64.rpm
rpm -Uvh ucl-1.03-2.el7.rf.x86_64.rpm
yum install ucl

wget -c http://ftp.tu-chemnitz.de/pub/linux/dag/redhat/el7/en/x86_64/rpmforge/RPMS/upx-3.91-1.el7.rf.x86_64.rpm
rpm -Uvh upx-3.91-1.el7.rf.x86_64.rpm
yum install upx

安装完成后执行upx如下:
在这里插入图片描述

1 直接压缩原始文件

将原始文件main高质量压缩:

 upx -9 main -o main.upx

生成的文件大小:1129624字节,压缩率:(2068291-1129624)/2068291*100=45.38%

2 直接压缩编译优化的文件
upx -9 main.release -o main.release.upx

生成的文件大小:582140字节,压缩率:(2068291-582140)/2068291*100=71.85%

3 strip已经压缩的文件是不允许的
strip main.upx -p -o main.upx.strip

由于在upx加壳压缩时已经将符号表和节内容删除了,再次strip时报了错
在这里插入图片描述

1.6.3 减小文件大小的小结
1 带参编译,直接去掉符号表等参数-w -s
2 使用upx加壳压缩文件,可以使用高质量-9参数压缩,压缩比可达70%左右

2、面试知识点

2.1 基础知识

2.1.1 关键字
1) new和make的区别

new内建函数,初始化一个指向类型的指针(*T),传递给new的是一个类型,返回的是这个新分配的零值的指针
make内建函数,作用于slice,map或chan,初始化并返回引用(T),返回一个初始化的实例

2 ) 数组和切片的区别

数组是值传递,内置类型;具有固定的长度且拥有0个后者多个相同数据类型元素的序列。
切片,表示拥有相同类型元素的可变长度的序列,有三个属性:指针,长度和容量,不需要指定大小,是地址传递,在追加元素时如果容量cap不足时将按len的2倍扩容

3)go中的引用类型

包括,数据切片、字典、通道、接口interface

4)go触发panic的场景

使用空指针,下标越界,调用panic函数

5)defer函数

defer函数属于延迟函数执行,多个defer函数之间按照LIFO先进后出的顺序执行

2.1.2 堆和栈的问题

i. go语言的编译器会选择把一个变量放在堆上还是栈空间上,编译器会做逃逸分析,当编译发现变量的作用域没有逃逸函数的范围,就可以在栈上,反之则必须分配在堆上
ii. 以下情况,变量一定被分配到堆上
a.如果变量被取地址,并且被逃逸分析识别为“逃逸到堆”
b.如果一个变量很大

2.1.3 性能调试

i. 查看panic的调用栈
ii. pprof
iii. 火焰图(配合压力测试)
iv. 使用go run -race 或者 go build -race 来进行竞争检测

2.2 channel的特性

1)给nil的channel写数据,永远阻塞
2)从一个nil的channel读数据,永远阻塞
3)给一个close的channel写数据,引起panic
4)从关闭的channel中读数据,如果缓冲区为空,返回一个nil
5)无缓冲的channel是同步的,而有缓冲区的channel是异步的
ch := make(chan interface{}) 和 ch := make(chan interface{},1)的区别?
无缓冲的必须要一直有接收者才行,要么会阻塞数据写入;
有缓冲的当写入的一个值尚未被拿走的时候,写数据失败,才会被阻塞。

2.3. 协程的调度原理

2.3.1 什么是协程

协程拥有自己的寄存器和上下文栈。协程调度切换时保存寄存器和上下文状态,再次切换回来时恢复寄存器和上下文。这个切换是在go的runtime调度的,无需系统内核切换,消耗的资源(cpu)就很小了,因此比较线程和进程来说,协程拥有强大的并发能力。

2.3.2 go的调度器——GPM模型

go的调度器内部有四个重要的结构:M、P、S、Sched:
M:表示内核级线程,一个M就是一个线程,goroutine就是运行在M之上的;M内部维护了许多小对象内存cache、当前执行的goroutine、随机数发生器等
G:表示一个goroutine,它拥有自己的栈,以及其他的信息,用于调度
P:Processor,主要用来直接goroutine,维护了一个goroutine队列,里面存储了所有需要执行的goroutine
Sched:调度器,他维护存储有M和G的队列以及调度器的一些状态信息
3)调度实现

2.3.3 gc的理解

未完待续。。。

2.3.4 常用的模型
1)select+for
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值