协程(goroutine)的作用
在执行A函数的时候,可以随时中断,去执行B函数,然后中断继续执行A函数(可以自动切换),
注意这一切换过程并不是函数调用(没有调用函数),过程很像多线程,然而协程中只有一个线程在执行(协程的本质是单个线程)
开启一个协程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| func test() { for i := 0; i < 10; i++ { fmt.Println("test func:", strconv.Itoa(i)) time.Sleep(time.Second * 1) } } func main() { go test()
go func(){ fmt.Println(111) }()
for i := 0; i < 5; i++ { fmt.Println("main func:", strconv.Itoa(i)) time.Sleep(time.Second * 1) } fmt.Println("exit...") }
|
启动多个协程
1 2 3 4 5 6 7
| for i := 0; i < 5; i++ { go func(i int) { fmt.Println("test func:", strconv.Itoa(i)) }(i) } time.Sleep(time.Second * 1)
|
使用WaitGoroutine退出协程
Waitgroutine用于等待一组协程的结束.父线程调用Add方法来设定应等待的写成的数量.
每个被等待的协程在结束时应调用Done方法.同时,主线程里可以调用Wait方法阻塞至所有线程结束.
解决主线程在协程还没有结束的时候先结束.
在sync
包下有WaitGroup
结构体,
这个结构体下有三个方法:
func(*WaitGroup)Add(delta int)
func(*WaitGroup)Done()
func(*WaitGroup)Wait()
如果开始就知道协程次数的情况下可以先操作Add()
Add()中加入的数字和协程的次数一定要保持一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var wg sync.WaitGroup func main() { for i := 0; i < 5; i++ { wg.Add(1) go func(n int) { defer wg.Done() fmt.Println(n) }(i) } wg.Wait() fmt.Println("exit...") }
|
多个协程操作同一个数据
此处出错的可能性 因为 每个协程的循环进行了三步操作 先取值 再加一或减一 再赋值
可能一个协程在取完值没赋值之前另外一个协程也开始取值后赋值 就覆盖了前面协程的值 所以会出错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| var lock sync.Mutex
var wg sync.WaitGroup var totalNum int
func add() { defer wg.Done() for i := 0; i < 100000; i++ { lock.Lock() totalNum = totalNum + 1 lock.Unlock() } } func sub() { defer wg.Done() for i := 0; i < 100000; i++ { lock.Lock() totalNum = totalNum - 1 lock.Unlock() } } func main() { wg.Add(2) go add() go sub() wg.Wait() fmt.Println(totalNum) fmt.Println("exit...") }
|
解决以上问题
使用互斥锁,确保一个协程在执行逻辑的时候另一个协程等待
使用sync
包的Mutex
结构体
互斥锁要加锁解锁,性能和效率相对较低
两个方法:
- func(m *Mutex) Lock()
- func(m *Mutex) Unlock()
如果读多写少需要用读写锁
在读的时候不产生影响,在写和读之前才会产生影响
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| var wg sync.WaitGroup
var lock sync.RWMutex
func read() { defer wg.Done() lock.RLock() fmt.Println("开始读数据") time.Sleep(time.Second) fmt.Println("读取完成") lock.RUnlock() } func write() { defer wg.Done() lock.Lock() fmt.Println("开始写操作") time.Sleep(time.Second * 3) lock.Unlock() } func main() { wg.Add(6) for i := 0; i < 5; i++ { go read() } go write() wg.Wait() fmt.Println("exit...") }
|
用defer + recover 来解决多个协程中某一个出错影响所有进程的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| var wg sync.WaitGroup func printNum() { defer wg.Done() for i := 1; i <= 10; i++ { fmt.Println("printnum :", strconv.Itoa(i)) } } func chufa() { defer wg.Done() defer func() { err := recover() if err != nil { fmt.Println("程序出错了:", err) return } }() num1 := 10 num2 := 0 fmt.Println("chufa :", strconv.Itoa(num1/num2)) } func main() { wg.Add(2) go printNum() go chufa() wg.Wait() fmt.Println("exit...") }
|