目录
一、goroutine性能对比
1. 素数算法
package main
import "fmt"
func main() {
// 获取1-100之间的素数:除了1和它本身整除外不能被任何数整除,1不是素数
for i := 2; i <= 100; i++ {
var flag bool = true // 假设是素数
for j := 2; j < i; j++ { // j从2开始,是因为1可以被0除外的所有数整除
if i%j == 0 {
flag = false
}
}
if flag {
fmt.Println(i)
}
}
}
2. for获取1-120000素数的执行时间
package main
import "time"
func main() {
start := time.Now().Unix()
for i := 2; i <= 120000; i++ {
var flag bool = true // 假设是素数
for j := 2; j < i; j++ {
if i%j == 0 {
flag = false
}
}
if flag {
//fmt.Println(i)
}
}
end := time.Now().Unix()
fmt.Println(end - start) // 50毫秒 49毫秒 因为单线程占用cpu较少所以执行时间较长 下面开启协程实验一下
}
3. 添加goroutine后,获取执行时间
package main
import (
"time"
"sync"
)
var wg sync.WaitGroup
/*
同时开启4个协程执行获取素数,并计算执行时间
1. 1-30000
2. 30001 -- 60000
3. 60001 -- 90000
4. 90001 -- 120000
*/
var wg sync.WaitGroup
func test(n int) {
defer wg.Done()
for i := (n-1)*30000 + 1; i < n*30000; i++ {
if i > 1 {
var flag = true
for j := 1; j < i; j++ {
if i%j == 0 {
flag = false
}
}
if flag {
}
}
}
}
func main() {
start := time.Now().Unix()
wg.Add(1)
for i := 1; i <= 4; i++ {
go test(i)
}
wg.Wait()
end := time.Now().Unix()
fmt.Println(end - start) // 4毫秒 3毫秒 速度提升很明显
}
二、Channel管道
管道是 Golang 在语言级别上提供的 goroutine 间的通讯方式,我们可以使用 channel 在 多个 goroutine 之间传递消息。如果说 goroutine 是 Go 程序并发的执行体,channel 就是它们 之间的连接。channel 是可以让一个 goroutine 发送特定值到另一个 goroutine 的通信机制。
Go 语言中的管道(channel)是一种特殊的类型。管道像一个传送带或者队列,总是遵 循先入先出(First In First Out)的规则,保证收发数据的顺序。每一个管道都是一个具体类 型的导管,也就是声明 channel 的时候需要为其指定元素类型。
所有管道均是先进先出,后进后出。
1. 定义管道
定义管道可以是任意类型
// 定义管道
var ch chan int
var ch1 chan bool
var ch2 chan string
var ch3 chan []int
2. 管道基本操作
定义管道要用make,赋值取值 <-
// 创建管道
ch = make(chan int, 3) // 定义管道
// 管道赋值
ch <- 10
ch <- 15
// 管道取值
m1 := <-ch
m2 := <-ch
fmt.Println(m1, m2) //10 15
// 管道值
fmt.Printf("值:%v 容量:%v 长度:%v\n", ch, cap(ch), len(ch)) // 值:0xc0000b6000 容量:3 长度:0
3. 管道类型
管道为引用数据类型
// 管道类型:引用类型
ch1 := make(chan int, 4)
ch1 <- 11
ch1 <- 12
ch2 := ch1
ch2 <- 13
<-ch2 // 11被取出 此时管道内为 12 13
m3 := <-ch2
m4 := <-ch1
fmt.Println(m3, m4) // 12 13
4. 管道阻塞
阻塞分为:有缓冲阻塞和无缓冲阻塞;另外多存多取都会导致管道的阻塞
// 2. 管道阻塞
// 1)无缓冲管道:如果创建管道时没有指定容量,那么就是一个无缓冲管道,也叫阻塞的管道
ch3 := make(chan int)
ch3 <- 10
fmt.Println("...") // fatal error: all goroutines are asleep - deadlock!
// 2)有缓冲管道:创建管道时指定了容量。但是赋值不能超过管道容量,同样如果管道中值被取完了再取也会造成管道阻塞
ch4 := make(chan int, 2)
ch4 <- 10
ch4 <- 20
ch4 <- 30 // fatal error: all goroutines are asleep - deadlock! 超出容量,管道阻塞
<-ch4
<-ch4
<-ch4 // fatal error: all goroutines are asleep - deadlock! 多取,管道阻塞
解决管道阻塞方法:
// 3)管道阻塞解决办法:存一个取一个,像水流一样,就不会造成阻塞。
ch5 := make(chan int, 3)
ch5 <- 10
<-ch5
ch5 <- 15
<-ch5
ch5 <- 14
<-ch5
ch5 <- 11
<-ch5
5. 循环取管道值
用for range取的时候 要先关闭管道 close(channel)
// 3. 从管道中循环取值
ch6 := make(chan int, 10)
for i := 0; i < 10; i++ {
ch6 <- i
}
// 用for range循环取数据 管道必须执行关闭 close
for v := range ch6 {
fmt.Println(v) // fatal error: all goroutines are asleep - deadlock!
}
// 但是用for循环就可以不关闭管道
j := len(ch6)
for i := 0; i < j; i++ {
fmt.Println(<-ch6) // 0 1 2 3 4 5 6 7 8 9
}
// for range正确示范
close(ch6)
for v := range ch6 {
fmt.Println(v) // 0 1 2 3 4 5 6 7 8 9
}
6. 结合goroutine实现管道一直运行
开启goroutine 让水管一直进一直出
进出时间一致,那么就会一直出一直进;如果进的慢出得快,那么出的就会等待先进去;如果进的快出的慢,那么只要不超过自身容量,会一直出去完毕的。
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
// 进出时间一致,那么就会一直出一直进;如果进的慢出得快,那么出的就会等待先进去;如果进的快出的慢,那么只要不超过自身容量,会一直出去完毕的。
// 进
func fn1(ch chan int) {
for i := 0; i < 10; i++ {
ch <- i
fmt.Println("写入成功。。。", i)
time.Sleep(time.Millisecond * 10)
}
close(ch)
wg.Done()
}
// 出
func fn2(ch chan int) {
for v := range ch {
fmt.Println(v, "出")
time.Sleep(time.Microsecond * 1000)
}
wg.Done()
}
func main() {
// 4. 开启goroutine 让水管一直进一直出
var ch = make(chan int, 10)
wg.Add(1)
go fn1(ch)
wg.Add(1)
go fn2(ch)
wg.Wait()
}
三、结合goroutine和channel统计1-120000的素数
思路图:
实现:
package main
import (
"sync"
"fmt"
)
var wg sync.WaitGroup
// putNum 存放数字的channel
func putNum(intChan chan int) {
for i := 2; i < 120000; i++ {
intChan <- i
}
close(intChan)
wg.Done()
}
//primeNum 统计素数
func primeNum(intChan chan int, primeChan chan int, boolChan chan bool) {
for v := range intChan {
var flag = true
for i := 2; i < v; i++ {
if v%i == 0 {
// 不是素数
flag = false
}
}
if flag {
primeChan <- v
}
}
// 每执行完一个就写入一个true
boolChan <- true
wg.Done()
}
// printNum 打印素数
func printNum(primeChan chan int) {
for v := range primeChan { // 如果使用forr来打印,那就得先关闭管道。
fmt.Println(v)
}
wg.Done()
}
func main() {
// 1. 要有一个intChan 存放1-120000数字
intChan := make(chan int, 1000)
// 2.primeChan 存放计算后的素数
primeChan := make(chan int, 1000)
// 3. boolChan 判断何时关闭管道
boolChan := make(chan bool, 16)
// 4. 进行所有数字的写入
wg.Add(1)
go putNum(intChan)
// 5. 同时开启16个线程进行计算
for i := 0; i < 16; i++ {
wg.Add(1)
go primeNum(intChan, primeChan, boolChan)
}
// 6. 把获取到素数打印出来
wg.Add(1)
go printNum(primeChan) // 此时没有关闭管道,所以会报deadlock!
// 7. 判断何时关闭管道
wg.Add(1)
go func() {
for i := 0; i < 16; i++ {
<-boolChan
}
// 当上面执行完了,就可以关闭primeChan了
close(primeChan)
wg.Done()
}() // 这样上面的打印 就不会报deadlock了。
wg.Wait()
fmt.Println("执行完毕。。。")
}