一. 下载GO
- go安装文件以及源代码
Downloads - The Go Programming Language
- 下载对应平台的二进制文件以及安装
- 环境变量
GOROOT: go的安装目录
GOPATH: src 存放源代码
pkg 存放依赖包
bin 存放可执行文件
其他常用变量: GOOS GOARCH GOPROXY
GOPROXY建议使用: export GOPROXY=https://goproxy.cn
二. IDE设置
下载并安装 visual studio code
Visual Studio Code - Code Editing. Redefined
安装go语言插件
三.GO语言一些基本命令
- build: 编译包和依赖
- clean: 移除对象文件
- doc: 显示包或者符号的文档
- env: 打印go的环境信息
- bug: 启动错误报告
- fix: 运行go tool fix
- fmt: 运行gofmt进行格式化
- generate: 从processing source生成go文件
- get: 下载并安装包和依赖
- install: 编译并安装包和依赖
- list: 列出包
- run: 编译并运行go程序
- test: 运行测试
- tool: 运行go提供的工具
- version: 显示go的版本
- vet: 运行go tool vet
- mod: module maintenance
四. Go语言的基本控制结构
1. If
if condition1 {
// do something
} else if condition2 {
// do something else
} else {
// catch-all or default
}
if v:= x; v < 0 {
// do something
}
2. Switch
switch var1 {
case val1:
case var2:
fallthrough
case val3:
f()
default:
...
}
3. for
for i := 0; i < 100; i++ {
sum += 1
}
for ; sum < 100; {
sum += 10
}
for {
if condition {
break
}
// do something
}
五. Go语言的数据结构
1. 变量和常量
const identifier type
var identifier type
2. 定义示例
myArray := [3] int{1, 2, 3}
myslice := []int{1, 2, 3} 或者 myslice := myArray[:]
// 切片添加
myslice = append(myslice[:], 1)
// 切片删除
myslice = append(myslice[:index], myslice[index + 1:]...)var c, java, python bool
c = false
java = true
python = true
var i, j int = 1, 2
c, python := 3, falsemyMap := make(map[string]string, 10)
myMap["a"] = "b"
myDict := map[int]int {2: 3, 3: 4}
myFuncDict := map[string]func() int {
"look": func() int {return 1},
}
// 可以使用下面方式判断key在不在
value, exists := myMap["a"]
if exists: {
fmt.Println(value)
}type Human struct {
first, last string
}
func (h *Human) getName() string {
return h.first + h.last + " "
}
type IF interface {
getName() string
}
interfaces := []IF{}
h := new(Human)
h.first = "ri"
h.last = "bbon"
interfaces = append(interfaces, h)
for _, it := range interfaces {
fmt.Println(it.getName())
}
// 结构体标签
type Mystruct struct {
Name string `json:"name"`
}
mt := Mystruct{Name: "aa"}
myType := reflect.TypeOf(mt)
name := myType.Field(0)
tag := name.Tag.Get("json")
fmt.Println(tag)
3. 类型转换
var i int = 40
var f float64 = float64(i) 或者 f := float64(i)
4. Make 和 New
New是返回指针的地址
Make则为返回第一个元素,可预设内存空间,避免未来的内存拷贝
六. Go语言函数
1. Go语言传参方式
// method 1
fmt.Println("os args is:", os.Args)
//method 2
name := flag.String("name", "ribbon", "input you english name")
flag.Parse()
2. Init函数
Init函数会在包初始化的时候运行
当多个依赖引用统一的项目, 且被引用项目的初始化在init中完成,并且不可重复运行时,会导致启动错误
var myVariable int = 0
func init() {
myVariable = 1
}
3. 传递变长参数
func append(slice []Type, elems ...Type) []Type
4. 回调函数
func Callback(x int, f func(int, int)) {
f(x, x)
}
5. 方法接口
方法: 作用在接受者上的函数
func (recv receiver_type) method(parameter_list) (return_value_list)
package main
import "fmt"
type Human struct {
first, last string
}
func (h *Human) getName() string {
return h.first + " " + h.last
}
type IF interface {
getName() string
}
type Car struct {
first1, last1 string
}
func (c Car) getName() string {
return c.first1 + " " + c.last1
}
func main() {
interfaces := []IF{}
h := new(Human)
h.first = "ri"
h.last = "bbon"
c := new(Car)
c.first1 = "car"
c.last1 = "car1"
interfaces = append(interfaces, h)
interfaces = append(interfaces, c)
for _, it := range interfaces {
fmt.Println(it.getName())
}
g := Human{}
g.first = "aa"
g.last = "cc"
fmt.Println(g.getName())
c = new(Car)
c.first1 = "ri"
c.last1 = "bbon"
fmt.Println(c.getName())
}
6. 反射机制
TypeOf 返回被检查对象的类型
ValueOf返回被检查对象的值
package main
import (
"fmt"
"reflect"
)
type T struct {
A string
b string
}
func (t T) GetString() string {
return t.A + "-----"
}
func main() {
my_map := make(map[string] string, 10)
my_map["a"] = "b"
t := reflect.TypeOf(my_map)
v := reflect.ValueOf(my_map)
fmt.Println(t, v, v.NumMethod())
my_struct := T{A: "11", b: "cc"}
fmt.Println(reflect.TypeOf(my_struct))
v = reflect.ValueOf(my_struct)
fmt.Println(v.NumMethod(), v.NumField(),
v.Method(0).Call(nil), v.Field(0),
v.Field(1))
}
7. Go语言中的面向对象编程
- 可见性控制
- public - 常量、变量、类型、接口、结构、函数等的名称大写
- private - 非大写的只能包內使用
- 继承
- 通过组合实现, 内嵌一个或者多个struct
- 多态
- 通过接口实现,通过接口定义方法集, 编写多套实现
8. Json编解码
Marshal: 从struct --> string
Unmarshal: 从string --> struct
package main
import (
"encoding/json"
"fmt"
"reflect"
)
type Human struct {
Name string
Age int
}
func StringToStruct(humanStr string) Human {
h := Human{}
err := json.Unmarshal([]byte(humanStr), &h)
if err != nil {
fmt.Println(err)
}
return h
}
func structToString(h Human) string {
h.Age = 30000
h.Name = "ribbon"
updateBytes, err := json.Marshal(h)
if err != nil {
fmt.Println(err)
}
return string(updateBytes)
}
func main() {
h := Human{}
s := structToString(h)
fmt.Println(s)
human := StringToStruct(s)
fmt.Println(reflect.ValueOf(human))
}
Json包使用map[string]interface{} 和 []interface{} 类型保存任意对象
package main
import (
"encoding/json"
"fmt"
)
type Human struct {
Name string
Age int
}
func structToString(h Human) string {
h.Age = 30000
h.Name = "ribbon"
updateBytes, err := json.Marshal(h)
if err != nil {
fmt.Println(err)
}
return string(updateBytes)
}
func main() {
h := Human{}
s := structToString(h)
fmt.Println(s)
var obj interface{}
err := json.Unmarshal([]byte(s), &obj)
if err != nil {
return
}
objMap, _ := obj.(map[string]interface{})
for key, value := range objMap {
switch value.(type) {
case string:
fmt.Printf("%s: %s ", key, value)
case int:
fmt.Printf("%s: %d ", key, value)
default:
fmt.Printf("%s: %v ", key, value)
}
}
var obj1 map[string] interface{}
err = json.Unmarshal([]byte(s), &obj1)
if err != nil {
return
}
for key, value := range obj1 {
switch value.(type) {
case string:
fmt.Printf("%s: %s ", key, value)
case int:
fmt.Printf("%s: %d ", key, value)
default:
fmt.Printf("%s: %v ", key, value)
}
}
}
9. 错误处理
Go没有内置的exception机制, 只提供error接口供定义错误
type error interface {
Error() string
}
可以通过error.New or fmt.Errorf创建新的error
通常应用程序对error的处理大部分是判断error是否为nil
10. defer Panic recover
defer这个相当于python java的finally
panic:可以再系统出现不可恢复错误的时候主动调用panic, panic会使当前线程直接crash
recover: 函数从panic或者错误场景中恢复
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
} ()
panic("\njson is error\n")
七. 多线程
1. 并发和并行
2. 协程
协程属于轻量级线程
Go语言在runtime、系统调用等多方面对goroutine调度进行了封装和处理,当遇到长时间执行或者进行系统调用时,会主动把当前goroutine的CPU(P)转让出去, 让其他goroutine能被调度,也就是golang从语言层面支持了协程
每个goroutine默认占用内存远比线程的小(goroutine:2KB 线程:8MB)
goroutine切换开销方面远比线程小(线程涉及切换【从用户态切换到内核态】、16个寄存器、PC/SP等寄存器刷新, goroutine只涉及PC/SP/DX三个寄存器值得修改)
package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 10; i++ {
go fmt.Println(i)
}
time.Sleep(time.Second)
}
3. CSP
描述两个独立的并发实体通过共享的通讯channel进行通信的并发模型
协程之间虽然解耦,但是它们和Channel有着耦合
package main
import (
"fmt"
)
func main() {
ch := make(chan int)
go func() {
ch <- 0
} ()
fmt.Println(<-ch)
}
waitGroup
package main
import (
"fmt"
"sync"
)
type worker struct {
in chan int
done func()
}
func dowork(id int, work worker) {
for i := range work.in{
work.done()
fmt.Printf("%d %c\n", id, i)
}
}
func createWorker(id int, wg *sync.WaitGroup) worker{
w := worker{
in: make(chan int),
done: func() {
defer wg.Done()
},
}
go dowork(id, w)
return w
}
func chanDemo() {
var wg sync.WaitGroup
var worker[10] worker
for i:=0; i<10; i++{
worker[i] = createWorker(i, &wg)
}
wg.Add(30)
for i, work := range worker{
work.in <- 'T'+ i
}
for i:=0; i<10; i++{
worker[i].in <- 'B' + i
}
for i:=0; i<10; i++{
worker[i].in <- 'a' + i
}
wg.Wait()
}
func main() {
chanDemo()
}
select
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
go func() {
ch <- 0
} ()
fmt.Println(<-ch)
ch2 := make(chan int, 10)
ch3 := make(chan int, 10)
tm := time.After(10 * time.Second)
go func() {
for i := 0; i < 10; i++ {
ch2 <- i
}
close(ch2)
} ()
go func() {
for i := 10; i < 20; i++ {
ch3 <- i
}
close(ch3)
} ()
for {
select {
case v := <-ch2:
fmt.Println("ch2", v)
case v := <-ch3:
fmt.Println("ch3", v)
case <- tm:
return
}
}
}
context
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("监控退出,停止了...")
return
default:
fmt.Println("goroutine监控中...")
time.Sleep(2 * time.Second)
}
}
}(ctx)
time.Sleep(10 * time.Second)
fmt.Println("可以了,通知监控停止")
cancel()
//为了检测监控过是否停止,如果没有监控输出,就表示停止了
time.Sleep(5 * time.Second)
}
4. 线程加锁
package main
func main() {
unsafeWrite()
}
func unsafeWrite() {
conflictMap := map[int]int{}
for i := 0; i <= 100; i++ {
go func() {
conflictMap[1] = i
}()
}
}
当多线程对同一个程序进行赋值操作的时候,会导致如下图的异常报错信息,需要添加锁进制在代码中。
- GO语言不仅仅提供基于CSP的通讯模型,也支持基于共享内存的多线程数据访问
- Sync 包提供了锁的基本原语
sync.Mutex互斥锁
package main
import (
"fmt"
"sync"
"time"
)
type SafeMap struct {
safeMap map[int]int
Mutex sync.Mutex
}
func safeWrite() {
conflictMap := SafeMap{safeMap: map[int]int{}, Mutex:sync.Mutex{}}
for i := 0; i <= 100; i++ {
go func() {
conflictMap.Mutex.Lock()
conflictMap.safeMap[1] = i
conflictMap.Mutex.Unlock()
}()
}
time.Sleep(time.Second)
fmt.Println(conflictMap.safeMap[0], conflictMap.safeMap[1])
}
func main() {
safeWrite()
}
sync.RWMutex 读写分离锁
package main
import (
"fmt"
"sync"
"time"
)
type SafeMap struct {
safeMap map[int]int
Mutex *sync.RWMutex
}
func safeWrite(conflictMap SafeMap, i int) {
conflictMap.Mutex.Lock()
conflictMap.safeMap[1] = i
conflictMap.Mutex.Unlock()
}
func safeRead(conflictMap SafeMap) {
conflictMap.Mutex.RLock()
fmt.Println(conflictMap.safeMap[1])
conflictMap.Mutex.RUnlock()
}
func main() {
conflictMap := SafeMap{safeMap: map[int]int{}, Mutex: &sync.RWMutex{}}
for i := 0; i <= 100; i++ {
go safeWrite(conflictMap, i)
go safeRead(conflictMap)
}
time.Sleep(time.Second)
}
sync.WaitGroup 等待一组group返回
package main
import (
"fmt"
"sync"
)
func main() {
waitGroup := sync.WaitGroup{}
waitGroup.Add(100)
for i := 0; i <= 100; i++ {
go func(i int) {
fmt.Println(i)
waitGroup.Done()
} (i)
}
waitGroup.Wait()
}
sync.Once 保证某段代码只执行一次
package main
import (
"fmt"
"sync"
)
func main() {
var once sync.Once
onceBody := func() {
fmt.Println("Only once")
}
done := make(chan bool)
for i := 0; i < 10; i++ {
go func() {
once.Do(onceBody)
done <- true
}()
}
for i := 0; i < 10; i++ {
<-done
}
}
sync.Cond 让一组goroutine在满足特定条件的时候被唤醒, k8s中的队列, 标准的生产者消费者模式
package main
import (
"fmt"
"sync"
"time"
)
type Queue struct {
queue []string
cond *sync.Cond
}
func (q *Queue) Enqueue (item string) {
q.cond.L.Lock()
defer q.cond.L.Unlock()
q.queue = append(q.queue, item)
q.cond.Broadcast()
}
func (q *Queue) Dequeue () string{
q.cond.L.Lock()
defer q.cond.L.Unlock()
if len(q.queue) == 0 {
fmt.Println("no data avaliable, wait")
q.cond.Wait()
}
result := q.queue[0]
q.queue = q.queue[1:]
return result
}
func main() {
q := Queue{queue: []string{}, cond: sync.NewCond(&sync.Mutex{})}
go func() {
for {
q.Enqueue("a")
time.Sleep(time.Second * 2)
}
} ()
go func() {
for {
result := q.Dequeue()
fmt.Println(result)
time.Sleep(time.Second * 2)
}
} ()
time.Sleep(time.Second * 10)
}
5. 线程调度
进程: 资源分配的基本单位
线程: 调度的基本单位
无论是线程还是进程,在linux中以task_strruct描述, 从内核角度看,与进程无本质区别。Glibc中的pthread库提供NPTL(Native POSIX Threading Library)支持
进程中有自己的mm(内存模型)、 fs(文件系统)、file(文件)、signal(信号量),独立维护, 而线程则共用同一套mm(内存模型)、fs(文件系统)、 file(文件) 、signal(信号量)。所谓线程即是没有独立资源的进程。
Linux内存调用
通过objdump 来看编译的二进制文件, 在这里定义了很多text .bss .data, text指指令有多大,data表示有多少初始化数据的值, bss表示有多少未初始化的值,dec指前面三个值得总和。
物理内存按照4096字节来进行分页,物理内存和虚拟内存是通过多级页表(常见的为四级页表)来进行对应,通过索引找到对应的page table再加上offset找到真正的物理地址。
CPU对内存的访问
CPU把虚拟地址传给MMU(内存管理单元), MMU去物理内存查询页表,得到实际的物理地址。CPU会维护TLB(缓存虚拟地址和物理地址的映射关系)
进程切换开销
- 直接开销
- 切换页表全局目录(PGD)
- 切换内核态堆栈
- 切换硬件上下文(进程恢复前,必须装入寄存器的数据统称硬件上下文)
- 刷新TLB
- 系统调度器的代码执行(TEXT)
- 间接开销
- CPU缓存失效导致进程需要到内存直接访问的IO操作变多
线程切换开销
线程相对于进程间节省了虚拟地址空间的转换。
用户线程
八. 内存管理
两种常见的内存管理方法:堆和内存池_Melody1994的博客_内存堆和内存池 可以参考这篇文章
TCMalloc机制文章:图解 TCMalloc - 知乎
Go语言内存分配: 图解Go语言内存分配 - 知乎
Go语言GC工作原理: Go 语言GC(垃圾回收)的工作原理 - 简书
指针的对象是在堆上面这个是需要GC回收的,局部变量对象(类似于struct)是在栈上当使用函数结束则生命周期也就结束
九. 包引用和依赖管理
当前只要学习gomod 或者 使用原生的GOROOT GOPATH来进行维护就可以,个人推荐使用go mod的方式
1. go mod使用方式
- 切换mod开启模式: export GO111MODULE=on/off/auto. 在/etc/profile文件上添加
- 使用GOPROVATE来指定私有代码仓库
- 创建项目在GOPATH/src目录下
- 初始化Go模块
- 下载依赖包添加缺少的依赖包瘦身, 会下载到GOPATH/pkg/mod目录下
- Go依赖添加到vendor目录下
- 可以看go.mod文件看使用GO版本和下载的包的版本, 如果要下载其他版本在init后先修改对应的版本号
- 可以使用replace来指定代码仓库或者指定版本