美文网首页
Golang 基础

Golang 基础

作者: 哓晓的故事 | 来源:发表于2019-05-31 09:43 被阅读0次

    1. log(github.com/sirupsen/logrus)

    log.Fatal 会直接中断当前服务, 即使是用 go func(){log.Fatal("end")}() 也会中断整个服务

    2. goroutine

    并发线程有3种模型(用户空间 - 系统空间):

    1. 内核级线程模型 1(系统线程-轻量级线程):1(用户线程) context switch 性能低
    2. 用户级线程模型 1(系统线程-轻量级线程):N(用户线程) context switch 成本低(实际由于无法有效利用多处理器, 效率低)
    3. 混合型线程模型 N(系统线程-轻量级线程):M(用户线程) golang的goroutine(coroutine)语义实现了此模型

    3. channel

    FIFO(管道先进先出)

    for v := range c {} 这段代码, 即使c已经没有数据进入, 也不会中断, 只有当使用 close(c) 主动关闭 channel, 才会中断这个死循环

    for { select {case data, flag := <- m.c} } 这段代码主动close(c), 会让返回的flag=false, 为了避免死循环, 应该主动的作出动作. 而整个for循环要退出, 最好做一个退出的event channel作为monitor, select本身随机的从多个case里面挑选一个执行

    q := make(chan string) 默认只有0个空间的chan(串行)

    如果不启动一个go func(){}(<-q)去消费, 直接使用q<-"s", 会导致进程被block(类似于没有打开任何通道, 没有办法进入数据), 编译器有时能检测到没有任何的goroutine, 导致deadlock

    此种类型保证数据被消费后, 再继续执行

    q := make(chan string, 1) 含有1个空间的chan(并行, 并非并行度越大越好)

    这时允许有一个buffer的空间不导致block

    此方法允许存在一定数据buffer

    没有 q<-"s" 直接使用 <-q 也会 block, 因此建议使用 go func(){}()实现协同编程

    4. init()

    一個Package下是不可以有重複的變數或者是函式名稱

    init() 可以在同一個 package 內宣定义多次

    init() 執行的時機會是在執行 main func 之前

    5. 指针变量

    *demo.Name = "ss" 是会直接修改指针指向对象的值

    // 骚气的传递
    func (s *ShardWorker) callStub(request interface{}) (interface{}, error) {
        return s.backend.sendGeneralRequest(request)
    }
    
    func (s *ShardWorker) PingNode(request *index_rpc.PingNodeRequest) (*index_rpc.PingNodeResponse, error) {
        v, err := s.callStub(request)
        if err != nil {
            return nil, err
        }
        return v.(*index_rpc.PingNodeResponse), nil
    }
    

    6. timer(定时器)/ticker(断路器)

    定时器(定时n后触发一次)

    //初始化定时器
    t := time.NewTimer(2 * time.Second)
    //当前时间
    now := time.Now()
    fmt.Printf("Now time : %v.\n", now)
    
    expire := <- t.C // t.C 阻塞监听定时器事件, 只会触发一次
    fmt.Printf("Expiration time: %v.\n", expire)
    

    断路器(每隔n后触发一次)

    //初始化断续器,间隔2s
    var ticker *time.Ticker = time.NewTicker(1 * time.Second)
    
    go func() {
        // ticker.C 阻塞监听定时器事件, 隔一段时间触发一次
        for t := range ticker.C {
            fmt.Println("Tick at", t)
        }
    }()
    
    time.Sleep(time.Second * 5)   //阻塞,则执行次数为sleep的休眠时间/ticker的时间
    ticker.Stop()
    fmt.Println("Ticker stopped")
    

    7. runtime.LockOSThread()

    golang的scheduler可以理解为公平协作调度和抢占的综合体,他不支持优先级调度。当你开了几十万个goroutine,并且大多数协程已经在runq等待调度了, 那么如果你有一个重要的周期性的协程需要优先执行该怎么办?

    使用runtime.LockOSThread() 该方法的作用是可以让当前协程绑定并独立一个线程M, 子协程不会继承lockOSThread特性

    可以借助runtime.LockOSThread()方法来绑定线程,绑定线程M后的好处在于,他可以由system kernel内核来调度,因为他本质是线程

    tid: 当使用 goroutine, tid会新建. 当不使用 goroutine, tid会继承
    pid: 使用/不使用 goroutine, pid会继承
    ppid: 使用/不使用 goroutine, ppid会继承
    

    8. defer

    defer 类似于 java 中的finally, 在一个 func block 结束之前, 会自动执行

    如果一个func内部有多个defer, defer是按照Stack进行存储的

    // 打印是 s1    close s1 v2 close s1 v1
    func s1() {
        print("s1\t")
        defer print("close s1 v1\t")
        defer println("close s1 v2")
    }
    

    8. Mutex & RWMutex

    读多写少的情况下, 尽可能的使用sync.RWMutex, 在 read 操作的时候, 使用RLock(), 在 set 操作的时候, 使用Lock()

    不要在一个 for { select mu.Lock() defer mu.Unlock()}, 因为这本身是一个func, 在func结束前是不会释放锁的

    mutex 不具备 goroutine 的重入特性

    8.1 Mutex

    使用 Mutex.Lock 会使所有的协程等待这个 mutext 的释放, 实现并发编程(非常的重, 此方法会导致用户空间->系统空间context switch, 性能很差)

    • Mutex 为互斥锁,Lock() 加锁,Unlock() 解锁
    • 在一个 goroutine 获得 Mutex 后,其他 goroutine 只能等到这个goroutine 释放该 Mutex
    • 使用 Lock() 加锁后,不能再继续对其加锁,直到利用 Unlock() 解锁后才能再加锁
    • 在 Lock() 之前使用 Unlock() 会导致 panic 异常
    • 已经锁定的 Mutex 并不与特定的 goroutine 相关联,这样可以利用一个 goroutine 对其加锁,再利用其他 goroutine 对其解锁
    • 在同一个 goroutine 中的 Mutex 解锁之前再次进行加锁,会导致死锁
    • 适用于读写不确定,并且只有一个读或者写场景
    // 不需要主动显示的 new一个对象, 只需要保存变量即可
    var mu sync.Mutext
    func hi() {
        mu.Lock()
        defer mu.Lock()
    }
    

    8.2 RWMutex

    • RWMutex 是单写多读锁,该锁可以加多个读锁或者一个写锁
    • 读锁占用的情况下会阻止写,不会阻止读,多个 goroutine 可以同时获取读锁
    • 写锁会阻止其他 goroutine(无论读和写)进来,整个锁由该 goroutine 独占
    • 适用于读多写少场景

    8.2.1 Lock() 和 Unlock()

    • Lock() 加写锁,Unlock() 解写锁
    • 如果在加写锁之前已经有其他的读锁和写锁,则 Lock() 会阻塞直到该锁可用,为- 确保该锁可用,已经阻塞的 Lock() 调用会从获得的锁中排除新的读取器,即写锁权限高于读锁,有写锁时优先进行写锁定
    • 在 Lock() 之前使用 Unlock() 会导致 panic 异常

    8.2.2 RLock() 和 RUnlock()

    • RLock() 加读锁,RUnlock() 解读锁
    • RLock() 加读锁时,如果存在写锁,则无法加读锁;当只有读锁或者没有锁时,可以加读锁,读锁可以加载多个
    • RUnlock() 解读锁,RUnlock() 撤销单词 RLock() 调用,对于其他同时存在的读锁则没有效果
    • 在没有读锁的情况下调用 RUnlock() 会导致 panic 错误
    • RUnlock() 的个数不得多于 RLock(),否则会导致 panic 错误

    9. WaitGroup

    这个demo每隔一秒, 会输出一个数字, 所有数字输出完成后, 结束集成

    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(idx int) {
            d := time.Duration(idx)
            time.Sleep(d * time.Second)
            println(idx)
            wg.Done()
        }(i)
    }
    wg.Wait()
    

    wg.add代表有增加n个正在处理的值, 当wg.wait时发现值不为0, 就会wait住, 当wg.done的时候,会对原子计数器做-1操作(有点类似Java中的CountDownLatch或者CyclicBarrier)

    10. unsafe.Pointer

    golang 内存对齐优化

    Go语言是个强类型语言。也就是说Go对类型要求严格,不同类型不能进行赋值操作。指针也是具有明确类型的对象,进行严格类型检查

    str := C.CString("xxx") 没有进行内存释放, 需要在后面跟上 defer C.free(unsafe.Pointer(str)) 主动释放内存

    unsafe.Pointer它表示任意类型且可寻址的指针值,可以在不同的指针类型之间进行转换(类似 C 语言的 void * 的用途)

    uintptr(unsafe.Pointer(z))变量表示Pointer对象所处的内存地址

    • 任何类型的指针值都可以转换为 Pointer
    • Pointer 可以转换为任何类型的指针值
    • uintptr 可以转换为 Pointer
    • Pointer 可以转换为 uintptr
    u := uint32(32)
    i := int32(1)
    fmt.Println(&u, &i) // 打印出地址
    p := &i // p 的类型是 *int32
    p = &u // 不能赋值
    p = (*int32)(&u) // 强转也不能
    p = (*int32)(unsafe.Pointer(&u)) // 可以赋值
    

    11. iota

    常量计数器, 只能在常量的表达式中使用

    // 每次 const 出现时,都会让 iota 初始化为0
    const (
        OutMute AudioOutput = iota // 0
        OutMono                    // 1
        OutStereo                  // 2
        _
        _
        OutSurround                // 5
    )
    

    相关文章

      网友评论

          本文标题:Golang 基础

          本文链接:https://www.haomeiwen.com/subject/omnitctx.html