案例 1:多线程
package main
import "fmt"
import "sync"
var mu sync.Mutex;
func doSum(num *int, wg *sync.WaitGroup) {
// mutex
mu.Lock();
*num++;
mu.Unlock();
defer wg.Done()
}
func main() {
var a int = 0;
var wg sync.WaitGroup
for i := 0; i<100; i++ {
wg.Add(1)
go doSum(&a, &wg);
}
fmt.Println("sum is ", a); // 大概率不是100
wg.Wait()
fmt.Println("sum is definitely", a) // 100,绝对的
}
- 通过 wg.Wait 确保go routine 全部结束,否则会阻塞后面的打印
- 通过 mutex 确保 num 变量的读写原子性,虽然是一行代码,如果没有 mutex,num++实际上是对应,读取,加 1,写入3 个操作,线程切换会分割这 3 步,导致结果不为 100
案例 2:通道——无缓冲
- 正确的实现
package main
import "fmt"
func main() {
ch := make(chan int) // 无缓冲
// 先启动接收者
go func() {
fmt.Println("等待数据...")
data := <-ch
fmt.Println("收到:", data)
}()
// 再发送数据
fmt.Println("发送数据...")
ch <- 42
fmt.Println("发送完成")
}
- go func() {}() 实际上是启动(准备了一个goroutine),放到了 goroutine队列
- chan<- 会阻塞 goroutine,如果这个代码在主 goroutine ,主的停了,逻辑处理器会去队列找一个其它的可以执行的 goroutine,这时候就会启动 func,读取。
vs
- 错误的实现
package main
import "fmt"
func main() {
ch := make(chan int) // 无缓冲
// 发送数据
fmt.Println("发送数据...")
ch <- 42 // 这里阻塞
fmt.Println("发送完成")
// 启动接收者,没机会启动啊,上面的写入通道直接阻塞了后面的代码执行,goroutine 都没机会进入队列
go func() {
fmt.Println("等待数据...")
data := <-ch
fmt.Println("收到:", data)
}()
}
结论:先启动接受者,在执行发送消息,或者先启动发送者 子 goroutine,再读取。即非缓冲 chan,读写都是会阻塞的,必须有另一个提前启动
- 通道——缓冲区通道
- 缓冲channel缓冲区未满时,可以自由读写
- 只有缓冲区满时发送才阻塞
- 只有缓冲区空时接收才阻塞