一. 下载GO

  • go安装文件以及源代码

Downloads - The Go Programming Language

  • 下载对应平台的二进制文件以及安装
  • 环境变量

        GOROOT: go的安装目录

        GOPATH: src 存放源代码

                            pkg 存放依赖包

                            bin 存放可执行文件

        其他常用变量: GOOS GOARCH GOPROXY

        GOPROXY建议使用: export GOPROXY=https://goproxy.cn

Go语言编程之旅 下载 go语言教程下载_json

二. IDE设置

下载并安装 visual studio code

Visual Studio Code - Code Editing. Redefined

安装go语言插件

Go - Visual Studio Marketplace

三.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. 并发和并行

Go语言编程之旅 下载 go语言教程下载_i++_02

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语言编程之旅 下载 go语言教程下载_Go语言编程之旅 下载_03

  • 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)支持

Go语言编程之旅 下载 go语言教程下载_i++_04

Go语言编程之旅 下载 go语言教程下载_go语言_05

进程中有自己的mm(内存模型)、 fs(文件系统)、file(文件)、signal(信号量),独立维护, 而线程则共用同一套mm(内存模型)、fs(文件系统)、 file(文件) 、signal(信号量)。所谓线程即是没有独立资源的进程。

Linux内存调用

Go语言编程之旅 下载 go语言教程下载_go语言_06

Go语言编程之旅 下载 go语言教程下载_Go语言编程之旅 下载_07

Go语言编程之旅 下载 go语言教程下载_json_08

       通过objdump 来看编译的二进制文件, 在这里定义了很多text .bss .data, text指指令有多大,data表示有多少初始化数据的值, bss表示有多少未初始化的值,dec指前面三个值得总和。

        

Go语言编程之旅 下载 go语言教程下载_Go_09

       物理内存按照4096字节来进行分页,物理内存和虚拟内存是通过多级页表(常见的为四级页表)来进行对应,通过索引找到对应的page table再加上offset找到真正的物理地址。

CPU对内存的访问

Go语言编程之旅 下载 go语言教程下载_Go_10

 CPU把虚拟地址传给MMU(内存管理单元), MMU去物理内存查询页表,得到实际的物理地址。CPU会维护TLB(缓存虚拟地址和物理地址的映射关系) 

进程切换开销

  • 直接开销
  • 切换页表全局目录(PGD)
  • 切换内核态堆栈
  • 切换硬件上下文(进程恢复前,必须装入寄存器的数据统称硬件上下文)
  • 刷新TLB
  • 系统调度器的代码执行(TEXT)
  • 间接开销
  • CPU缓存失效导致进程需要到内存直接访问的IO操作变多


线程切换开销

        线程相对于进程间节省了虚拟地址空间的转换。


用户线程

Go语言编程之旅 下载 go语言教程下载_go语言_11


八. 内存管理

两种常见的内存管理方法:堆和内存池_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文件上添加        
        

Go语言编程之旅 下载 go语言教程下载_i++_12

  • 使用GOPROVATE来指定私有代码仓库

Go语言编程之旅 下载 go语言教程下载_Go_13

  • 创建项目在GOPATH/src目录下

Go语言编程之旅 下载 go语言教程下载_Go_14

  • 初始化Go模块

Go语言编程之旅 下载 go语言教程下载_go语言_15


  • 下载依赖包添加缺少的依赖包瘦身, 会下载到GOPATH/pkg/mod目录下

Go语言编程之旅 下载 go语言教程下载_Go语言编程之旅 下载_16

  • Go依赖添加到vendor目录下

Go语言编程之旅 下载 go语言教程下载_Go语言编程之旅 下载_17

  • 可以看go.mod文件看使用GO版本和下载的包的版本, 如果要下载其他版本在init后先修改对应的版本号

Go语言编程之旅 下载 go语言教程下载_go语言_18

  •   可以使用replace来指定代码仓库或者指定版本

Go语言编程之旅 下载 go语言教程下载_go语言_19