27 goroutine channel实现并发和并行(四)

在前面的章节中介绍了打印1-100000之间的素数的三种方法中的前两种;尽管使用了goroutine,开了四个协程之后,运算时间在一定程度上压缩了不少。但是,单单使用goroutine无法实现各个数据之间的共享,今天尝试在此基础上寻找更优解:

  1. 传统方法,通过for循环遍历各个数
  2. 使用并发或并行,将统计素数的任务分配给多个goroutine去完成
  3. goroutine结合channel
goroutine结合channel
var wg sync.WaitGroup

func putNum(intChan chan int) {
    for i := 2; i < 100000; i++ {
        intChan <- i
    }
    close(intChan)
    wg.Done()
}

//判断intChan是否为素数,如果是素数,放到primeNum中
func primeNum(intChan chan int, primeChan chan int, exitChan chan bool) {
    for num := range intChan {
        var flag = true
        for i := 2; i < num; i++ {
            if num%i == 0 {
                flag = false
                break
            }
        }
        if flag {
            //fmt.Println(num, "是素数")
            primeChan <- num
        }
    }
    //报错send on closed channel如果一个chanel关闭,就无法再发送数据了
    //close(primeChan)
    exitChan <- true
    wg.Done()
}

func printPrime(primeChan chan int) {
    //for v := range primeChan {
    //fmt.Println(v)
    //}
    wg.Done()
}

func main() {
    //协程数
    channelNum := 10
    start := time.Now().UnixNano()
    //存放数字
    intChan := make(chan int, 1000)
    //存放素数
    primeChan := make(chan int, 10000)
    //标识primeChan是否关闭
    exitChan := make(chan bool, channelNum)
    //存放数字的协程
    wg.Add(1)
    go putNum(intChan)
    for i := 0; i < channelNum; i++ {
        //统计素数的协程
        wg.Add(1)
        go primeNum(intChan, primeChan, exitChan)
    }

    //打印素数的协程
    wg.Add(1)
    go printPrime(primeChan)

    //判断exitChan是否存满值,如果存慢值,则表示协程执行结束
    wg.Add(1)
    go func() {
        for i := 0; i < channelNum; i++ {
            <-exitChan
        }
        //关闭primeChan
        close(primeChan)
        wg.Done()
    }()
    wg.Wait()
    end := time.Now().UnixNano()
    fmt.Println("end...", (end-start)/1e6)
    //end... 622
}

保持计算量不变的情况下,开启10个协程之后,计算时间压缩到了622ms

select多路复用

在某些场景下,需要同时从多个通道接收数据,这时就用到了select多路复用

select {
    case <-ch1:
        ...
    case data := <-ch2
        ...
    case ch3<-data:
        ...
    default:
        默认操作
}
  • 使用select来获取channel时,不需要关闭channel
func main() {
    //定义一个管道 10个数据int
    intChan := make(chan int, 10)
    for i := 0; i < 10; i++ {
        intChan <- i
    }
    //定义一个管道 5个string数据
    stringChan := make(chan string, 5)
    for i := 0; i < 5; i++ {
        stringChan <- "hello" + fmt.Sprintf("%d", i)
    }

    close(intChan)

    for {
        select {
        case v := <-intChan:
            fmt.Println("从intChan读取的数据", v)
            time.Sleep(time.Millisecond * 50)
        case v := <-stringChan:
            fmt.Println("从stringChan读取的数据",v)
        default:
            fmt.Printf("数据获取完毕")
            return
        }
    }
}
//从stringChan读取的数据 hello0
//从stringChan读取的数据 hello1
//从intChan读取的数据 0
//从stringChan读取的数据 hello2
//从stringChan读取的数据 hello3
//从stringChan读取的数据 hello4
//从intChan读取的数据 1
//从intChan读取的数据 2
//从intChan读取的数据 3
//从intChan读取的数据 4
//从intChan读取的数据 5
//从intChan读取的数据 6
//从intChan读取的数据 7
//从intChan读取的数据 8
//从intChan读取的数据 9
//数据获取完毕

你可能感兴趣的:(27 goroutine channel实现并发和并行(四))