0
点赞
收藏
分享

微信扫一扫

【云原生训练营】模块二 Go语言进阶

英乐 2022-05-04 阅读 20

这里写目录标题

  • 函数调用
  • 常用语法
  • 多线程
  • 深入理解channel
  • 基于channel编写一个生产者消费者程序

函数

Main函数

  • 每个 Go 语言程序都应该有个 main package
  • Main package 里的 main 函数是 Go 语言程序入口
package main
func main() {
	args := os.Args
	if len(args) != 0 {
		println("Do not accept any argument")
		os.Exit(1)
	}
	println("Hello world")
}

init函数

  • Init 函数:会在包初始化时运行
  • 谨慎使用 init 函数
    • 当多个依赖项目引用统一项目,且被引用项目的初始化在 init 中完成,并且不可重复运行时,会导
      致启动错误
package main
var myVariable = 0
func init() {
	myVariable = 1
}

传递变长参数

Go 语言中的可变长参数允许调用方传递任意多个相同类型的参数

  • 函数定义
func append(slice []Type, elems ...Type) []Type
  • 调用方法
myArray := []string{}
myArray = append(myArray, "a","b","c")

内置函数

在这里插入图片描述

回调函数

函数作为参数传入其它函数,并在其他函数内部调用执行

  • strings.IndexFunc(line, unicode.IsSpace)
  • Kubernetes controller的leaderelection

示例:

func main() {
	DoOperation(1, increase)
	DoOperation(1, decrease)
}
func increase(a, b int) {
	println(“increase result is:, a+b)
}
func DoOperation(y int, f func(int, int)) {
	f(y, 1)
}
func decrease(a, b int) {
	println("decrease result is:", a-b)
}

闭包

匿名函数

  • 不能独立存在
  • 可以赋值给其他变量
    x:= func(){}
  • 可以直接调用
    func(x,y int){println(x+y)}(1,2)
  • 可作为函数返回值
    func Add() (func(b int) int
    在这里插入图片描述

接口

  • 接口定义一组方法集合

    type IF interface {
    	Method1(param_list) return_type
    }
    
  • 适用场景:Kubernetes 中有大量的接口抽象和多种实现

  • Struct 无需显示声明实现 interface,只需直接实现方法

  • Struct 除实现 interface 定义的接口外,还可以有额外的方法

  • 一个类型可实现多个接口(Go 语言的多重继承)

  • Go 语言中接口不接受属性定义

  • 接口可以嵌套其他接口

  • 注意事项

    • Interface 是可能为 nil 的,所以针对 interface 的使用一定要预
      先判空,否则会引起程序 crash(nil panic)
    • Struct 初始化意味着空间分配,对 struct 的引用不会出现空指针

在这里插入图片描述

反射机制

  • reflect.TypeOf ()返回被检查对象的类型
  • reflect.ValueOf()返回被检查对象的值
myMap := make(map[string]string, 10)
myMap["a"] = "b"
t := reflect.TypeOf(myMap)
fmt.Println("type:", t)
v := reflect.ValueOf(myMap)
fmt.Println("value:", v)

常用语法

错误处理

  • Go 语言无内置 exception 机制,只提供 error 接口供定义错误

    type error interface {
    Error() string
    }
    
  • 可通过 errors.New 或 fmt.Errorf 创建新的 error
    var errNotFound error = errors.New("NotFound")

  • 通常应用程序对 error 的处理大部分是判断 error 是否为 nil,
    如需将 error 归类,通常交给应用程序自定义,比如 kubernetes 自定义了与 apiserver 交互的不同类型错误。

type StatusError struct {
	ErrStatus metav1.Status
}
var _ error = &StatusError{}

// Error implements the Error interface.
func (e *StatusError) Error() string {
	return e.ErrStatus.Message
}

defer

函数返回之前执行某个语句或函数

  • 等同于 Java 和 C# 的 finally

常见的 defer 使用场景:记得关闭你打开的资源

  • defer file.Close()
  • defer mu.Unlock()
  • defer println(“”)

Panic和recover

  • panic: 可在系统出现不可恢复错误时主动调用 panic, panic 会使当前线程直接 crash
  • defer: 保证执行并把控制权交还给接收到 panic 的函数调用者
  • recover: 函数从 panic 或 错误场景中恢复
defer func() {
	fmt.Println("defer func is called")
	if err := recover(); err != nil {
		fmt.Println(err)
	}
	}()
panic("a panic is triggered")

线程加锁

理解线程安全
在这里插入图片描述

锁:

  • Go 语言保证线程安全,可以使用 channel 和 共享内存去保证。
  • Go 语言不仅仅提供基于 CSP 的通信模型,也支持基于共享内存的多线程数据访问,在Sync包提供了锁的基本原语。
  • sync.Mutex 互斥锁,Lock加锁,unlock解锁。不论读和写都是互斥的。
  • sync.RWMutex 读写分离锁,不限制并发读,只限制并发写和并发读写。
  • sync.WaitGroup 它的语意就是定义一个组,这个组里面会有假如100个线程,每个线程在结束时候都应该去调Done(),只有结束Done()减为0的时候才往下执行wait()
  • sync.Once 保证某段代码只执行一次
  • sync.Cond 让一组Goroutine 在满足特定条件时被唤醒(生产者、消费者)
sync.NewCond(&sync.Mutex{})
package main

import (
	"fmt"
	"sync" 
	"time"
)

func main() {
	defer fmt.Println("1")
	defer fmt.Println("2")
	defer fmt.Println("3")
	loopFunc()
	time.Sleep(time.Second)
}

func loopFunc() {
	lock := sync.Mutex{}
	for i := 0; i < 3; i++ {
		// go func(i int) {
		lock.Lock()
		defer lock.Unlock()
		fmt.Println("loopFunc:", i)
		// }(i)
	}
}

mutex示例:

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	go rLock()
	go wLock()
	go lock()
	time.Sleep(5 * time.Second)
}

func lock() {
	lock := sync.Mutex{}
	for i := 0; i < 3; i++ {
		lock.Lock()
		defer lock.Unlock()
		fmt.Println("lock:", i)
	}
}

func rLock() {
	lock := sync.RWMutex{}
	for i := 0; i < 3; i++ {
		lock.RLock()
		defer lock.RUnlock()
		fmt.Println("rLock:", i)
	}
}

func wLock() {
	lock := sync.RWMutex{}
	for i := 0; i < 3; i++ {
		lock.Lock()
		defer lock.Unlock()
		fmt.Println("wLock:", i)
	}
}

cond示例:生产者与消费者

package main

import (
	"fmt"
	"sync"
	"time"
)

type Queue struct {
	queue []string
	cond  *sync.Cond
}

func main() {
	q := Queue{
		queue: []string{},
		cond:  sync.NewCond(&sync.Mutex{}),
	}
	go func() {
		for {
			q.Enqueue("a")
			time.Sleep(time.Second * 2)
		}
	}()
	for {
		q.Dequeue()
		time.Sleep(time.Second)
	}
}

func (q *Queue) Enqueue(item string) {
	q.cond.L.Lock()
	defer q.cond.L.Unlock()
	q.queue = append(q.queue, item)
	fmt.Printf("putting %s to queue, notify all\n", item)
	q.cond.Broadcast()
}

func (q *Queue) Dequeue() string {
	q.cond.L.Lock()
	defer q.cond.L.Unlock()
	for len(q.queue) == 0 {
		fmt.Println("no data available, wait")
		q.cond.Wait()
	}
	result := q.queue[0]
	q.queue = q.queue[1:]
	return result
}

线程调度

  • 进程:资源分配的基本单位
  • 线程:调度的基本单位
  • 无论是线程还是进程,在linux中都以task_strut描述,从内核角度看,与进程无本质区别。
  • Glibc中的pthread库提供NPTL(Native POSIX Threading Library)支持
    在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

用户线程:无需内核帮助,应用程序在用户空间创建的可执行单元,创建销毁完全在用户态完成,减少内核态(系统调用的依赖)的消耗。
在这里插入图片描述

Goroutine

Go 语言基于 GMP 模型实现用户态线程

  • G:表示 goroutine,每个 goroutine 都有自己的栈空间,定时器,
    初始化的栈空间在 2k 左右,空间会随着需求增长。
  • M(相当于CPU数):抽象化代表内核线程,记录内核线程栈信息,当 goroutine 调度
    到线程时,使用该 goroutine 自己的栈信息。
  • P:代表调度器,负责调度 goroutine,维护一个本地 goroutine 队
    列,M 从 P 上获得 goroutine 并执行,同时还负责部分内存的管理。

在这里插入图片描述
在这里插入图片描述

G所处的位置

  • 进程都有一个全局的 G 队列
  • 每个 P 拥有自己的本地执行队列
  • 有不在运行队列中的 G
    • 处于 channel 阻塞态的 G 被放在 sudog
    • 脱离 P 绑定在 M 上的 G,如系统调用
    • 为了复用,执行结束进入 P 的 gFree 列表中的 G

Goroutine 创建过程

  • 获取或者创建新的 Goroutine 结构体
    • 从处理器的 gFree 列表中查找空闲的 Goroutine
    • 如果不存在空闲的 Goroutine,会通过 runtime.malg 创建一个栈大小足够的新结构体
  • 将函数传入的参数移到 Goroutine 的栈上
  • 更新 Goroutine 调度相关的属性,更新状态为_Grunnable
  • 返回的 Goroutine 会存储到全局变量 allgs 中

将 Goroutine 放到运行队列上

  • Goroutine 设置到处理器的 runnext 作为下一个处理器执行的任务
  • 当处理器的本地运行队列已经没有剩余空间时(256),就会把本地队列中的一部分 Goroutine 和待加入的 Goroutine通过 runtime.runqputslow 添加到调度器持有的全局运行队列上

调度器行为

  • 为了保证公平,当全局运行队列中有待执行的 Goroutine 时,通过 schedtick 保证有一定
    几率(1/61)会从全局的运行队列中查找对应的 Goroutine
  • 从处理器本地的运行队列中查找待执行的 Goroutine
  • 如果前两种方法都没有找到 Goroutine,会通过 runtime.findrunnable 进行阻塞地查找
    Goroutine
    • 从本地运行队列、全局运行队列中查找
    • 从网络轮询器中查找是否有 Goroutine 等待运行
    • 通过 runtime.runqsteal 尝试从其他随机的处理器中窃取待运行的 Goroutine

课后练习

将练习1.2中的生产者消费者模型修改成为多个生产者和多个消费者模式

举报

相关推荐

0 条评论