• cloudenative1-2: go语言特性


    cloudenative1-2
    GO语言特性
    module1的第二次课
    ---------------------------
    5.函数
    module1/helloworld/main.go
    package main

    import (
        "flag"
        "fmt"
        "os"
    )

    func main() {
        name := flag.String("name", "world", "specify the name you want to say hi")
        flag.Parse()
        fmt.Println("os args is:", os.Args)
        fmt.Println("input parameter is:", *name)
        fullString := fmt.Sprintf("Hello %s from Go\n", *name)
        fmt.Println(fullString)
    }

    func DuplicateString(input string) (error, string) {
        if input == "aaa" {
            return fmt.Errorf("aaa is not allowed"), ""
        }
        return nil, input + input
    }
    $ go build main.go
    zxl@qwq:~/go/src/github.com/cncamp/golang/examples/module1/helloworld$ ./main
    os args is: [./main]
    input parameter is: world
    Hello world from Go

    $ ./main aaa
    os args is: [./main aaa]
    input parameter is: world
    Hello world from Go

    $ ./main --name jesse
    os args is: [./main --name jesse]
    input parameter is: jesse
    Hello jesse from Go

        err, result := DuplicateString("aaa")
        if err == nil {
            fmt.Println(result)
        } else {
            fmt.Println(err)
        }
    ---------------------------
    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")
    }
    ---------------------------
    参数解析
    -请注意 main 函数与其他语言不同,没有类似 java 的 []string args 参数
    -Go 语言如何传入参数呢?
    方法1:
    fmt.Println("os args is:", os.Args)
    方法2:
    name := flag.String("name", "world", "specify the name you want to say hi")
    flag.Parse()
    ---------------------------
    Init 函数
    -Init 函数:会在包初始化时运行
    -谨慎使用 init 函数
    --当多个依赖项目引用统一项目,且被引用项目的初始化在 init 中完成,并且不可重复运行时,会导
    致启动错误
    package main
    var myVariable = 0
    func init() {
        myVariable = 1
    }
    例子:module1/init/main.go
    package main

    import (
        "fmt"

        _ "github.com/cncamp/golang/examples/module1/init/a"
        _ "github.com/cncamp/golang/examples/module1/init/b"
    )

    func init() {
        fmt.Println("main init")
    }

    func main() {

    }
    init from b
    init from a
    main init

    kubernetes->glog->init->flag parse parameter
    kubernetes->a->vendor->glog->init->flag parse parameter
    a应该直接用上面的glog,不应该自己vendor一份
    glog已经废弃,切到klog
    ---------------------------
    返回值
    -多值返回
    •函数可以返回任意数量的返回值
    -命名返回值
    •Go 的返回值可被命名,它们会被视作定义在函数顶部的变量。
    •返回值的名称应当具有一定的意义,它可以作为文档使用。
    •没有参数的 return 语句返回已命名的返回值。也就是直接返回。
    -调用者忽略部分返回值
    result, _ = strconv.Atoi(origStr)
    ---------------------------
    传递变长参数
    Go 语言中的可变长参数允许调用方传递任意多个相同类型的参数
    • 函数定义
    func append(slice []Type, elems ...Type) []Type
    • 调用方法
    myArray := []string{}
    myArray = append(myArray, "a","b","c")
    ---------------------------
    内置函数
    close 管道关闭
    len, cap 返回数组、切片,Map 的长度或容量
    new, make 内存分配
    copy, append 操作切片
    panic, recover 错误处理
    print, println 打印
    complex, real, imag 操作复数
    ---------------------------
    回调函数(Callback)
    • 函数作为参数传入其它函数,并在其他函数内部调用执行
    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)
    •使用场景
    defer func() {
        if r := recover(); r != nil {
            println(“recovered in FuncX”)
        }
    }()
    ---------------------------
    方法
    • 方法:作用在接收者上的函数--为interface,struct定义方法
    func (recv receiver_type) methodName(parameter_list) (return_value_list)
    函数调用,是要告诉调用方调用哪个包里面的函数。
    method是定义在接收者上面的。
    •使用场景
    -很多场景下,函数需要的上下文可以保存在receiver属性中,
    通过定义 receiver 的方法,该方法可以直接访问 receiver 属性,减少参数传递需求
    // StartTLS starts TLS on a server from NewUnstartedServer.
    func (s *Server) StartTLS() {
        if s.URL != “” {
            panic(“Server already started”)
        }
        if s.client == nil {
        s.client = &http.Client{Transport: &http.Transport{}}
    }
    ---------------------------
    传值还是传指针
    • Go 语言只有一种规则-传值
    • 函数内修改参数的值不会影响函数外原始变量的值
    • 可以传递指针参数将变量地址传递给调用函数,Go 语言会
    复制该指针作为函数内的地址,但指向同一地址
    • 思考:当我们写代码的时候,函数的参数传递应该用struct
    还是pointer?

    例子:module1/pointer/main.go
    func changeParameter(para *ParameterStruct, value string) {
        para.Name = value
    }

    func cannotChangeParameter(para ParameterStruct, value string) {
        para.Name = value
    }
    ---------------------------
    接口
    • 接口定义一组方法集合
    type IF interface {
        Method1(param_list) return_type
    }
    • 适用场景:Kubernetes 中有大量的接口抽象和多种实现
    • Struct 无需显示声明实现 interface,只需直接实现方法
    • Struct 除实现 interface 定义的接口外,还可以有额外的方法
    • 一个类型可实现多个接口(Go 语言的多重继承)
    • Go 语言中接口不接受属性定义 -- JAVA在接口里面可以有property
    • 接口可以嵌套其他接口
    ---------------------------
    接口
    例子:module1\interface\main.go
    type IF interface {
        getName() string
    }
    type Human struct {
        firstName,lastName string
    }
    func (h *Human) getName() string {
        return h.firstName + "," + h.lastName
    }
    type Car struct {
        factory, model string
    }
    func (c *Car) getName() string {
        return c.factory + "-" + c.model
    }
    func main() {
        interfaces := []IF{}
        h := new(Human)
        h.firstName = "first"
        h.lastName = "last"
        interfaces = append(interfaces, h)
        c := new(Car)
        c.factory = "benz"
        c.model = "s"
        interfaces = append(interfaces, c)
        for _, f := range interfaces {
            fmt.Println(f.getName())
        }
        p := Plane{}
        p.vendor = "testVendor"
        p.model = "testModel"
        fmt.Println(p.getName())
    }
    ---------------------------
    注意事项
    •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)
    ---------------------------
    基于 struct 的反射
    // struct
    myStruct := T{A: "a"}
    v1 := reflect.ValueOf(myStruct)
    for i := 0; i < v1.NumField(); i++ {
        fmt.Printf("Field %d: %v\n", i, v1.Field(i))
    }
    for i := 0; i < v1.NumMethod(); i++ {
        fmt.Printf("Method %d: %v\n", i, v1.Method(i))
    }
    // 需要注意 receive 是 struct 还是指针
    result := v1.Method(0).Call(nil)
    fmt.Println("result:", result)
    反射机制有性能损耗
    ---------------------------
    Go 语言中的面向对象编程
    可见性控制
    •public - 常量、变量、类型、接口、结构、函数等的名称大写
    •private - 非大写就只能在包内使用
    继承
    •通过组合实现,内嵌一个或多个 struct
    多态
    •通过接口实现,通过接口定义方法集,编写多套实现
    ---------------------------
    Json 编解码
    •Unmarshal: 从 string 转换至 struct
    func unmarshal2Struct(humanStr string)Human {
        h := Human{}
        err := json.Unmarshal([]byte(humanStr), &h)
        if err != nil {
            println(err)
        }
        return h
    }
    •Marshal: 从 struct 转换至 string
    func marshal2JsonString(h Human) string {
        h.Age = 30
        updatedBytes, err := json.Marshal(&h)
        if err != nil {
            println(err)
        }
        return string(updatedBytes)
    }
    ---------------------------
    Json 编解码
    json 包使用 map[string]interface{} 和 []interface{} 类型保存任意对象
    •可通过如下逻辑解析任意 json
    var obj interface{}
    err := json.Unmarshal([]byte(humanStr), &obj)
    objMap, ok := obj.(map[string]interface{})
    for k, v := range objMap {
        switch value := v.(type) {
        case string:
            fmt.Printf("type of %s is string, value is %v\n", k, value)
        case interface{}:
            fmt.Printf("type of %s is interface{}, value is %v\n", k, value)
        default:
            fmt.Printf("type of %s is wrong, value is %v\n", k, value)
        }
    }
    ---------------------------
    6.常用语法
    ---------------------------
    错误处理
    •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")
    ---------------------------
    7.多线程
    ---------------------------
    并发和并行
    并发(concurrency)
    •两个或多个事件在同一时间间隔发生
    并行(parallellism)
    两个或者多个事件在同一时刻发生
    ---------------------------
    协程
    进程:
     •分配系统资源(CPU 时间、内存等)基本单位
     •有独立的内存空间,切换开销大
    线程:进程的一个执行流,是 CPU 调度并能独立运行的的基本单位
    •同一进程中的多线程共享内存空间,线程切换代价小
    •多线程通信方便
    •从内核层面来看线程其实也是一种特殊的进程,它跟父进程共享了打开的文件和文件系统信息,共
    享了地址空间和信号处理函数
    协程
    •Go 语言中的轻量级线程实现
    •Golang 在 runtime、系统调用等多方面对 goroutine 调度进行了封装和处理,当遇到长时间执行
    或者进行系统调用时,会主动把当前 goroutine 的 CPU (P) 转让出去,让其他 goroutine 能被调度
    并执行,也就是 Golang 从语言层面支持了协程
    ---------------------------
    Communicating Sequential Process
    CSP
    •描述两个独立的并发实体通过共享的通讯 channel 进行通信的并发模型。
    Go 协程 goroutine
    •是一种轻量线程,它不是操作系统的线程,而是将一个操作系统线程分段使用,通过调度器实现协
    作式调度。
    •是一种绿色线程,微线程,它与 Coroutine 协程也有区别,能够在发现堵塞后启动新的微线程。
    通道 channel
    •类似 Unix 的 Pipe,用于协程之间通讯和同步。
    •协程之间虽然解耦,但是它们和 Channel 有着耦合。
    ---------------------------
    线程和协程的差异
    每个 goroutine (协程) 默认占用内存远比 Java 、C 的线程少
    • goroutine:2KB
    • 线程:8MB
    线程/goroutine 切换开销方面,goroutine 远比线程小
    • 线程:涉及模式切换(从用户态切换到内核态)、16个寄存器、PC、SP...等寄存器的刷新
    • goroutine:只有三个寄存器的值修改 - PC / SP / DX.
    GOMAXPROCS
    •控制并行线程数量
    ---------------------------
    协程示例
    • 启动新协程:go functionName()
    for i := 0; i < 10; i++ {
        go fmt.Println(i)
    }
    time.Sleep(time.Second)
    module1/defer/main.go
        for i := 0; i < 3; i++ {
            go func(i int) {
                // lock.Lock()
                // defer lock.Unlock()
                fmt.Println("loopFunc:", i)
            }(i)
        }
    go fmt.println("loopFunc:",i) --乱序执行
    ---------------------------
    channel - 多线程通信
    Channel 是多个协程之间通讯的管道
    •一端发送数据,一端接收数据
    •同一时间只有一个协程可以访问数据,无共享内存模式可能出现的内存竞争
    •协调协程的执行顺序

    声明方式
    •var identifier chan datatype
    •操作符<-

    示例
    ch := make(chan int)
    go func() {
        fmt.Println("hello from goroutine")
        ch <- 0 //数据写入Channel
    }()
    i := <-ch//从Channel中取数据并赋值
    实现无锁方式消息传递
    ---------------------------
    通道缓冲
    • 基于 Channel 的通信是同步的
    • 当缓冲区满时,数据的发送是阻塞的
    • 通过 make 关键字创建通道时可定义缓冲区容量,默认缓冲区容量为 0
    下面两个定义的区别?
    •ch := make(chan int)
    •ch := make(chan int,1)
    ---------------------------
    遍历通道缓冲区
    ch := make(chan int, 10)
    go func() {
        for i := 0; i < 10; i++ {
            rand.Seed(time.Now().UnixNano())
            n := rand.Intn(10) // n will be between 0 and 10
            fmt.Println("putting: ", n)
            ch <- n
        }
        close(ch)
    }()
    fmt.Println("hello from main")
    for v := range ch {
        fmt.Println("receiving: ", v)
    }
    ---------------------------
    单向通道--配合双向通道使用
    只发送通道
    •var sendOnly chan<- int
    只接收通道
    •var readOnly <-chan int
    Istio webhook controller
    •func (w *WebhookCertPatcher) runWebhookController(stopChan <-chan struct{}) {}
    这个函数里面不能往通道里面写数据。

    如何用: 双向通道转换
    var c = make(chan int)
    go prod(c)
    go consume(c)
    func prod(ch chan<- int){  //转成发送通道
        for { ch <- 1 }
    }
    func consume(ch <-chan int) { //转成接收通道
        for { <-ch }
    }
    ---------------------------
    关闭通道
    •通道无需每次关闭
    •关闭的作用是告诉接收者该通道再无新数据发送
    •只有发送方需要关闭通道
    ch := make(chan int)
    defer close(ch)
    if v, notClosed := <-ch; notClosed {
        fmt.Println(v)
    }
    ---------------------------
    select
    当多个协程同时运行时,可通过 select 轮询多个通道
    •如果所有通道都阻塞则等待,如定义了 default 则执行 default
    •如多个通道就绪则随机选择
    select {
        case v:= <- ch1:
        ...
        case v:= <- ch2:
        ...
        default:
        ...
    }
    ---------------------------
    定时器 Timer
    time.Ticker 以指定的时间间隔重复的向通道 C 发送时间值
    使用场景
    •为协程设定超时时间
    timer := time.NewTimer(time.Second)
    select {
        // check normal channel
        case <-ch:
            fmt.Println("received from ch")
        case <-timer.C:
            fmt.Println("timeout waiting from channel ch")
    }
    ---------------------------
    ---------------------------
    上下文 Context
    超时、取消操作或者一些异常情况,往往需要进行抢占操作或者中断后续操作
    Context 是设置截止日期、同步信号,传递请求相关值的结构体
    type Context interface {
        Deadline() (deadline time.Time, ok bool)
        Done() <-chan struct{}
        Err() error
        Value(key interface{}) interface{}
    }
    用法
    •context.Background
    Background通常被用于主函数、初始化和测试中,作为一个顶层context
    •context.TODO
    TODO是在不确定使用什么context时才会用
    •context.WithDeadline
    超时时间
    •context.WithValue
    向context添加键值对
    •context.WithCancel
    可取消的context
    ---------------------------
    如何停止一个子协程
    done := make(chan bool)
    go func() {
        for {
            select {
                case <-done:
                    fmt.Println("done channel is triggerred, exit child go routine")
                    return
            }
        }
    }()
    close(done)
    ---------------------------
    基于 Context 停止子协程
    Context 是 Go 语言对 go routine 和 timer 的封装
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    go process(ctx, 100*time.Millisecond)
    <-ctx.Done()
    fmt.Println("main:", ctx.Err())
    1)基于context传递键值对
    module1/context/context/main.go
        baseCtx := context.Background()
        ctx := context.WithValue(baseCtx, "a", "b")
        go func(c context.Context) {
            fmt.Println(c.Value("a"))
        }(ctx)
    2)超时的context触发协程退出
        timeoutCtx, cancel := context.WithTimeout(baseCtx, time.Second)
        defer cancel()
        go func(ctx context.Context) {
            ticker := time.NewTicker(1 * time.Second)
            for _ = range ticker.C {
                select {
                case <-ctx.Done():
                    fmt.Println("child process interrupt...")
                    return
                default:
                    fmt.Println("enter default")
                }
            }
        }(timeoutCtx)
        select {
        case <-timeoutCtx.Done():
            time.Sleep(1 * time.Second)
            fmt.Println("main process exit!")
        }
    ---------------------------
    课后练习1.2
    • 基于 Channel 编写一个简单的单线程生产者消费者模型
    • 队列:
    队列长度10,队列元素类型为 int
    • 生产者:
    每1秒往队列中放入一个类型为 int 的元素,队列满时生产者可以阻塞
    • 消费者:
    每一秒从队列中获取一个元素并打印,队列为空时消费者阻塞
    ---------------------------
     

  • 相关阅读:
    php实战案例记录(20)时间比较
    Broker消息设计--Kafka从入门到精通(十三)
    leetcode 310周赛
    fiddler如何抓模拟器中APP的包
    GitHub访问慢解决办法
    面试公司ETL工程师(实习生)——笔试面试题(SQL)
    Servlet到底是什么(非常透彻)
    Oncommand解析代码
    常见的业务分析方法
    JVM-内存模型 面试总结
  • 原文地址:https://blog.csdn.net/hb_zxl/article/details/126575875