go 协程练习例题
- 例1:统计 1-200000 的数字中,哪些是素数
- 例2:使用单通道、2个协程交替读取字符串
- 例3:使用1个管道,2个协程写数据、1个协程读
- 例4:完成一个并发任务调度器,按照指定顺序执行一系列任务
- 例5:并发计算数组的和
例1:统计 1-200000 的数字中,哪些是素数
使用并发/并行的方式,将统计素数的任务分配给多个(4 个)goroutine 去完成。
package mainimport ("fmt""time"
)// 统计 1-8000 的数字中,哪些是素数
func main() {// 用来放1000个数据var intChan = make(chan int, 1000)// 存放最终的素数结果var primeChan = make(chan int, 2000)// 用来判断程序什么时候退出var exitChan = make(chan bool, 4)go PutNum(intChan)// 开4条来判断是否是素数的协程for i := 0; i < 4; i++ {go PrimeNum(intChan, primeChan, exitChan)}// 当我们可以从exitChan 中取出4个数据的时候,才可以关闭primeChango func() {// 这里会发生阻塞,当boolChan 可以取出四个数据后,程序继续向下运行for i := 0; i < 4; i++ {<-exitChan}// 当我们从exitChan 取出4的数据的时候,说明四条判断是否为素数的协程都以及运行完了,这时候就可以放心的关闭primeChan了close(primeChan)}()// 不断的从primeChan 当中取出数据,直到 primeChan 为空,而且primeChan已经关闭for {v, ok := <-primeChanif !ok {break}fmt.Print(v, " ")}}// 不断的写入 1-8000 的数据
func PutNum(intChan chan int) {for i := 1; i <= 8000; i++ {intChan <- i}close(intChan)
}// 判断 intChan 中的数据 是否为素数,如果是素数,就加到primeChan 中
func PrimeNum(intChan chan int, primeChan chan int, exitChan chan bool) {for {time.Sleep(time.Millisecond * 10)num, ok := <-intChan// 如果intChan管道里面没有数据了,跳出循环if !ok {break}// 判断取出的数是否为素数var flag bool = truefor i := 2; i < num; i++ {if num%i == 0 {flag = falsebreak}}// 如果是素数,就添加到 primeChan 中if flag {primeChan <- num}}fmt.Println("有一个 primeNum 协程因为取不到数据,退出")// 给 exitChan 添加一个数据,表示这条协程运行结束了exitChan <- true
}
例2:使用单通道、2个协程交替读取字符串
思路:两个协程并发,一个协程读完然后往通道里写数据,另一个协程写完然后读数据,两个协程交替进行,交替阻塞。(利用无缓冲通道的特性)
package mainimport ("fmt""sync"
)var dataCh = make(chan byte)
var sw sync.WaitGroup
var index int64 = 0
var str = "hello wrold!!"func main() {sw.Add(2)go desk1()go desk2()sw.Wait()
}func desk1() {defer sw.Done()for {v, ok := <-dataChif !ok {return}fmt.Println("desk1 读取数据,内容为", string(v))if index < int64(len(str)) {dataCh <- str[index]index++// atomic.AddInt64(&index, 1) // 原子操作,其实也不用进行原子操作,因为两个协程每次只运行一个,不会出现并发冲突} else {close(dataCh) // 两个协程都有 close(dataCh) ,但是只会执行一次,因为另一个close不会到达return}}
}func desk2() {defer sw.Done()for {if index < int64(len(str)) {dataCh <- str[index]index++// atomic.AddInt64(&index, 1)} else {close(dataCh)return}v, ok := <-dataChif !ok {return}fmt.Println("desk2 读取数据,内容为", string(v))}
}
例3:使用1个管道,2个协程写数据、1个协程读
注意通道关闭时机。
package mainimport ("fmt""sync""sync/atomic"
)var ch = make(chan int, 5) // 存数据
var sw sync.WaitGroup
var num int64 // 用来记录通道关闭的时机func main() {// 设置GMP中的P:更改参数为1或者2,然后比较一下从管道中取出来的数据顺序// runtime.GOMAXPROCS(1)sw.Add(1)go readData()sw.Add(2)go writeData(1, 10)go writeData(11, 20)sw.Wait()
}func readData() {defer sw.Done()// for v := range ch {// fmt.Println(v)// }// fmt.Println("readData 退出了")// 或者:不仅要会用 for ... range,还要多会用 for 和 select 的组合for {select {case v, ok := <-ch:if !ok {return}fmt.Println(v)}}
}func writeData(start, end int) {defer sw.Done()for i := start; i <= end; i++ {ch <- i}// 原子操作atomic.AddInt64(&num, 1)// 在第二个写数据的管道执行完毕之后才可以关闭 chif num == 2 {fmt.Println(num, "关闭了")close(ch)}
}
例4:完成一个并发任务调度器,按照指定顺序执行一系列任务
1、函数 AddTask(func()):向任务调度器添加任务。
2、函数 Start():启动任务调度器并开始执行任务。
3、要保证任务按照添加的顺序依次执行,即每个任务在前一个任务完成后才能开始执行。
package mainimport ("fmt""sync""time"
)type fu func()// 存放任务
var ch chan fuvar (addWg sync.WaitGroup // 添加任务executeWg sync.WaitGroup // 执行任务
)func init() {ch = make(chan fu, 4)
}// AddTask 添加任务
func AddTask(f fu) {ch <- f
}// Start 开始执行任务
func Start() {executeWg.Add(1)go func() {defer executeWg.Done()for f := range ch {f()}}()addWg.Wait() // 等待所有任务添加完毕executeWg.Wait() // 等待所有任务执行完毕fmt.Println("所有任务添加并执行完毕")
}func main() {addWg.Add(1)go func() {// 添加任务结束后关闭管道defer close(ch)defer addWg.Done() // 确保任务添加完毕后通知主协程AddTask(fu(func() {fmt.Println("执行任务1")}))AddTask(fu(func() {fmt.Println("执行任务2")}))AddTask(fu(func() {fmt.Println("执行任务3")}))// 休眠2s,表示这两秒内没有任务time.Sleep(2 * time.Second)AddTask(fu(func() {fmt.Println("执行任务4")}))time.Sleep(1 * time.Second)AddTask(fu(func() {fmt.Println("执行任务5")}))AddTask(fu(func() {fmt.Println("执行任务6")}))AddTask(fu(func() {fmt.Println("执行任务7")}))}()Start()
}
例5:并发计算数组的和
创建一个程序,计算一个大数组的和,使用多个 goroutine 分担计算任务。将数组分成若干部分,每个部分由一个 goroutine 计算其部分的和,然后将部分和合并得到最终结果。
package mainimport ("fmt""sync""sync/atomic"
)var sw sync.WaitGroup
var sum int32func main() {var sli = []int{1, 3, 453, 2, 5, 7, 3, 2, 6, 3, 6, 3, 6, 299, 2352, 2229, 23, 8}for i := 0; i < len(sli); i += 5 {sw.Add(1)if i+5 > len(sli) {go sumFu(sli[i:])} else {go sumFu(sli[i : i+5])}}sw.Wait()fmt.Println(sum) // 5411demo := 0for i := 0; i < len(sli); i++ {demo += sli[i]}fmt.Println(demo) // 5411
}func sumFu(sli []int) {defer sw.Done()var demo intfor _, v := range sli {demo += v}atomic.AddInt32(&sum, int32(demo))
}