golang高级进阶(二):goroutine性能、channel管道

目录

一、goroutine性能对比

1. 素数算法

2. for获取1-120000素数的执行时间

3. 添加goroutine后,获取执行时间

二、Channel管道

1. 定义管道

2. 管道基本操作

3. 管道类型

4. 管道阻塞

5. 循环取管道值

6. 结合goroutine实现管道一直运行

三、结合goroutine和channel统计1-120000的素数


一、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("执行完毕。。。")
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值