Golang 基础

哓晓的故事 · · 2038 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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 基础

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2038 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传