1. log(github.com/sirupsen/logrus)
log.Fatal
会直接中断当前服务, 即使是用 go func(){log.Fatal("end")}()
也会中断整个服务
2. goroutine
并发线程有3种模型(用户空间 - 系统空间):
- 内核级线程模型 1(系统线程-轻量级线程):1(用户线程) context switch 性能低
- 用户级线程模型 1(系统线程-轻量级线程):N(用户线程) context switch 成本低(实际由于无法有效利用多处理器, 效率低)
-
混合型线程模型 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
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
)
有疑问加站长微信联系(非本文作者)