go 协程练习例题
-
- [例1:统计 1-200000 的数字中,哪些是素数](#例1:统计 1-200000 的数字中,哪些是素数)
- 例2:使用单通道、2个协程交替读取字符串
- 例3:使用1个管道,2个协程写数据、1个协程读
- 例4:完成一个并发任务调度器,按照指定顺序执行一系列任务
- 例5:并发计算数组的和
例1:统计 1-200000 的数字中,哪些是素数
使用并发/并行的方式,将统计素数的任务分配给多个(4 个)goroutine 去完成。
go
package main
import (
"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个数据的时候,才可以关闭primeChan
go func() {
// 这里会发生阻塞,当boolChan 可以取出四个数据后,程序继续向下运行
for i := 0; i < 4; i++ {
<-exitChan
}
// 当我们从exitChan 取出4的数据的时候,说明四条判断是否为素数的协程都以及运行完了,这时候就可以放心的关闭primeChan了
close(primeChan)
}()
// 不断的从primeChan 当中取出数据,直到 primeChan 为空,而且primeChan已经关闭
for {
v, ok := <-primeChan
if !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 = true
for i := 2; i < num; i++ {
if num%i == 0 {
flag = false
break
}
}
// 如果是素数,就添加到 primeChan 中
if flag {
primeChan <- num
}
}
fmt.Println("有一个 primeNum 协程因为取不到数据,退出")
// 给 exitChan 添加一个数据,表示这条协程运行结束了
exitChan <- true
}
例2:使用单通道、2个协程交替读取字符串
思路:两个协程并发,一个协程读完然后往通道里写数据,另一个协程写完然后读数据,两个协程交替进行,交替阻塞。(利用无缓冲通道的特性)
go
package main
import (
"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 := <-dataCh
if !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 := <-dataCh
if !ok {
return
}
fmt.Println("desk2 读取数据,内容为", string(v))
}
}
例3:使用1个管道,2个协程写数据、1个协程读
注意通道关闭时机。
go
package main
import (
"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)
// 在第二个写数据的管道执行完毕之后才可以关闭 ch
if num == 2 {
fmt.Println(num, "关闭了")
close(ch)
}
}
例4:完成一个并发任务调度器,按照指定顺序执行一系列任务
1、函数 AddTask(func()):向任务调度器添加任务。
2、函数 Start():启动任务调度器并开始执行任务。
3、要保证任务按照添加的顺序依次执行,即每个任务在前一个任务完成后才能开始执行。
go
package main
import (
"fmt"
"sync"
"time"
)
type fu func()
// 存放任务
var ch chan fu
var (
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 计算其部分的和,然后将部分和合并得到最终结果。
go
package main
import (
"fmt"
"sync"
"sync/atomic"
)
var sw sync.WaitGroup
var sum int32
func 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) // 5411
demo := 0
for i := 0; i < len(sli); i++ {
demo += sli[i]
}
fmt.Println(demo) // 5411
}
func sumFu(sli []int) {
defer sw.Done()
var demo int
for _, v := range sli {
demo += v
}
atomic.AddInt32(&sum, int32(demo))
}