go 协程练习例题

go 协程练习例题

例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))
}
相关推荐
自信人间三百年3 小时前
数据结构和算法-06线段树-01
java·linux·开发语言·数据结构·算法·leetcode
银氨溶液6 小时前
RabbitMQ实现消息发送接收——实战篇(路由模式)
java·开发语言·后端·消息队列·rabbitmq·消息分发
昨天今天明天好多天6 小时前
【Go】Linux、Windows、Mac 搭建Go开发环境
linux·windows·golang
爱敲代码的小冰7 小时前
spring boot 过滤器
java·spring boot·后端
大梦百万秋7 小时前
Go 语言新手入门:快速掌握 Go 基础
开发语言·后端·golang
m0_748241707 小时前
前端视角下的Go语法学习:创建 Go 项目
前端·学习·golang
轩情吖7 小时前
C++STL之list(用法超详解)
开发语言·数据结构·c++·链表·迭代器·list·带头双向循环链表
keep.ac7 小时前
ArrayList源码分析、扩容机制面试题,数组和List的相互转换,ArrayList与LinkedList的区别
java·开发语言
夕泠爱吃糖7 小时前
C++中如何实现接口继承与实现继承,以及它们的区别?
开发语言·c++