golang 的并发
golang 的线程 和 普通的线程
- 普通的线程
- 系统级的线程都会有一个固定大小的栈(一般默认可能是2M),这个栈主要用来保存函数递归调用时参数和局部变量。
固定了栈的大小导致了两个问题: 一是对于很多只需要很小的栈空间的线程来说是一个巨大的浪费, 二是对于少数需要
巨大栈空间的线程来说又面临栈溢出的风险
- 系统级的线程都会有一个固定大小的栈(一般默认可能是2M),这个栈主要用来保存函数递归调用时参数和局部变量。
- goroutine 线程
- 一个 goroutine 会以一个很小的栈启动(可能 2KB 或 4KB), 当遇到深度递归导致当前栈空间不足时, goroutine 会
根据需要动态地伸缩栈的大小(最大值可达到1GB). 应为启动的代价很小, 所以我们可以启用很多 goroutine
- 一个 goroutine 会以一个很小的栈启动(可能 2KB 或 4KB), 当遇到深度递归导致当前栈空间不足时, goroutine 会
- 区别
- 从上面可以看出 普通的线程是要经过内核的, 是系统层次上的; goroutine 是不经过内核的, 是代码层次上的,
golang 运行时还包含了自己的调度器,只有在当前 goroutine 发生阻塞时才会导致调度,调度器会根据具体函数
只保存必要的寄存器,切换的代价要比系统线程低得多。
- 从上面可以看出 普通的线程是要经过内核的, 是系统层次上的; goroutine 是不经过内核的, 是代码层次上的,
并发编程
- sync.Mutex Lock() Unlock()
- sync.RWMutex RLock() RUnlock() 读写锁
- sync/atomic 原子操作
- sync.Once 执行一次
- atomic.Value Store() Load()
并发的安全退出
-
基于
select
实现的管道的超时判断:select { case v := <-in: fmt.Println(v) case <-time.After(time.Second): return // 超时 }
-
通过
select
的default
分支实现非阻塞的管道发送或接口操作:select { case v := <-in: fmt.Println(v) default: // 没有数据 }
-
通过
select
来阻止main
函数退出:func main(){ // do some tings select{} }
-
当有多个管道均可操作时,
select
会随机选择一个管道。基于该特性我们可以用select
实现一个生成随机数序列的程序func main(){ ch := make(chan int) go func(){ for { selct { case ch <- 0: case ch <- 1: } } }() for v := range ch { fmt.Println(v) } }
-
我们通过
select
和default
分支可以很容易实现一个Goroutine
的退出控制:func worker(channel chan bool) { for { select { default: fmt.Println("hello") // 正常工作 case <-cannel: // 退出 } } } func main(){ cannel := make(chan bool) go worker(cannel) time.Sleep(time.Second) cannel <- true }
但是管道的发送操作和接收操作是一一对应的, 如果要停止多个
Goroutine
那么可能需要创建同样数量的管道,
这个代价太大了。其实我们可以通过close
关闭一个管道来实现广播的效果,所有从关闭管道接口的操作均会收到一个零值
和一个可选的失败标志。func main(){ cancel := make(chan bool) for i := 0; i < 10; i++ { go worker(cancel) } time.Sleep(time.Second) close(cannel) }
我们通过
close
来关闭cancel
管道向多个Goroutine
广播退出的指令. 不过这个程序依然不够稳健:
当每个Goroutine
收到退出指令退出时一般会进行一定的清理工作, 但是退出的清理工作并不能保证被完成,因为
main
线程并没有等待各个工作Goroutine
退出工作完成的机制。我们可以结合sync.WaitGroup
来改进:func worker(wg *sync.WaitGroup, cannel chan bool) { defer wg.Done() for { select { default: fmt.Println("hello") case <-cannel: return } } } func main(){ canncel := make(chan bool) var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go worker(&wg, cannel) } time.Sleep(time.Second) close(cannel) wg.Wait() }
goroutine 相关方法
- runtime.GOMAXPROCS(num int) 用来设置或查询可以并发执行的 goroutine 数目, n >= 1 表示设置 GOMAXPROCS 值, 否则表示查询当前的 GOMAXPROCS 值
- runtime.Goexit() 结束当前 goroutine 的运行
- runtime.Gosched() 放弃当前调度执行机会, 将当前 goroutine 放到队列中等待下次被调度