本文主要是对 Generic Data Structures and Algorithms in Go 一书中的并发代码的学习。
go key word
go关键词可以新建一个协程运行代码
package main
import (
"fmt"
"time"
)
func regularFunction() {
fmt.Println("Just call regularFunction()")
time.Sleep(time.Second * 5)
}
func goroutineFunction() {
fmt.Println("Just called goroutineFunction()")
time.Sleep(time.Second * 3)
fmt.Println("goroutineFunction() end")
}
func main() {
go goroutineFunction()
fmt.Println("In main 1 line below goroutineFunction()")
regularFunction()
fmt.Println("In main 1 line below regularFuncion()")
}
如果主的协程不等待,那么当子的协程运行不完,整个程序也会结束,类似子线程没有join:
package main
import (
"fmt"
"time"
)
func goroutineFunction() {
fmt.Println("Just called goroutineFunction()")
time.Sleep(time.Second * 3)
fmt.Println("goroutineFunction() end")
}
func main() {
go goroutineFunction()
}
WaitGroup
WaitGroup可以用来等待多个子协程结束后,整个程序结束:
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
var wg sync.WaitGroup
func outputStrings() {
defer wg.Done()
strings := [5]string{"One", "Two", "Three", "Four", "Five"}
for i := 0; i < 5; i++ {
delay := 1 + rand.Intn(3)
time.Sleep(time.Duration(delay) * time.Second)
fmt.Println(strings[i])
}
}
func outputInts() {
defer wg.Done()
for i := 0; i < 5; i++ {
delay := 1 + rand.Intn(3)
time.Sleep(time.Duration(delay) * time.Second)
fmt.Println(i)
}
}
func main() {
wg.Add(2)
go outputStrings()
go outputInts()
wg.Wait() // 会阻塞等待结束
}
channel 可以用来同步协程之间的运行
死锁的情况:
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func pingGenerator(c chan string) {
defer wg.Done()
for i := 0; i < 5; i++ {
c <- "ping"
time.Sleep(time.Second * 1)
}
}
func output(c chan string) {
defer wg.Done()
for {
value := <-c
fmt.Println(value)
}
}
func main() {
c := make(chan string)
wg.Add(2)
go pingGenerator(c)
go output(c)
wg.Wait() // 会阻塞等待结束
}
这里output会在5个ping以后一直阻塞,造成整个程序的阻塞,导致所有的协程无法继续下去,造成了死锁。
解决的方法:
使用select语句:
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func pingGenerator(c chan string) {
defer wg.Done()
for i := 0; i < 5; i++ {
c <- "ping"
time.Sleep(time.Second * 1)
}
}
func output(c chan string) {
defer wg.Done()
for {
select {
case value := <-c:
fmt.Println(value)
case <-time.After(time.Second * 3):
fmt.Println("Program timed out.")
wg.Done()
}
}
}
func main() {
c := make(chan string)
wg.Add(2)
go pingGenerator(c)
go output(c)
wg.Wait() // 会阻塞等待结束
}
使用另一个channel,避免使用WaitGroup:
package main
import (
"fmt"
"time"
)
var quit = make(chan bool)
func pingGenerator(c chan string) {
for i := 0; i < 5; i++ {
c <- "ping"
time.Sleep(time.Second * 1)
}
}
func output(c chan string) {
for {
select {
case value := <-c:
fmt.Println(value)
case <-time.After(time.Second * 3):
fmt.Println("Program timed out.")
quit <- true
}
}
}
func main() {
c := make(chan string)
go pingGenerator(c)
go output(c)
<-quit
}
channel的缓冲区
如果不指定 channel 的缓冲区,那么只有一个,在往里面塞数据的时候,程序是会阻塞的
比如,如下代码会直接导致死锁,因为缓冲区已经满了
package main
var unbuf_chan = make(chan string)
func main() {
unbuf_chan <- "hello world"
}
下面的代码一样会死锁,因为缓冲区满了,这里需要比较小心,避免缓冲区满了的情况,及时消费掉(从channel取出来)
package main
var unbuf_chan = make(chan string, 1)
func main() {
unbuf_chan <- "hello world"
unbuf_chan <- "hello world"
}
Race Condition
类似线程不安全的情况:
package main
import (
"fmt"
"sync"
)
const (
number = 1000
)
var countValue int
func main() {
var wg sync.WaitGroup
wg.Add(number)
for i := 0; i < number; i++ {
go func() {
countValue++
wg.Done()
}()
}
wg.Wait()
fmt.Printf("countValue is %d\n", countValue)
}
可以通过 Mutex加锁解决:
package main
import (
"fmt"
"sync"
)
const (
number = 1000
)
var countValue int
var m sync.Mutex
func main() {
var wg sync.WaitGroup
wg.Add(number)
for i := 0; i < number; i++ {
go func() {
m.Lock() // 加锁 锁住临界区
countValue++
m.Unlock() // 释放锁
wg.Done()
}()
}
wg.Wait()
fmt.Printf("countValue is %d\n", countValue)
}
两个协程交替打印A和B的实现, 交替打印100个A和B
这个经典问题,使用channel和select 一起实现
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
var chan_a = make(chan struct{})
var chan_b = make(chan struct{})
func printA() {
for {
select {
case <-chan_b:
fmt.Println("A")
chan_a <- struct{}{}
}
}
}
func printB() {
i := 1
for {
select {
case <-chan_a:
fmt.Printf("%d: B\n", i)
chan_b <- struct{}{}
}
if i == 100 {
wg.Done()
return
}
i++
}
}
func main() {
wg.Add(1)
go printA()
go printB()
chan_a <- struct{}{}
wg.Wait()
}
这里 主方法中 如果
chan_a <- struct{}{}
放在
go printA()
之前 就会造成死锁,原因就是 channel 没有缓冲区,会阻塞住。
可以把 chan_a 设置为
var chan_a = make(chan struct{}, 1)
如下代码,就不会死锁了:
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
var chan_a = make(chan struct{}, 1)
var chan_b = make(chan struct{})
func printA() {
for {
select {
case <-chan_b:
fmt.Println("A")
chan_a <- struct{}{}
}
}
}
func printB() {
i := 1
for {
select {
case <-chan_a:
fmt.Printf("%d: B\n", i)
chan_b <- struct{}{}
}
if i == 100 {
wg.Done()
return
}
i++
}
}
func main() {
wg.Add(1)
chan_a <- struct{}{}
go printA()
go printB()
wg.Wait()
}