目录
使用select完善生产者和消费者模型,键盘输入回车终止数据。
生产者和消费者模型
-
需求: 计算一个数字的各个位数之和,例如数字123,结果为1+2+3=6 随机生成数字进行计算
第一种方式
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
type Obj struct {
id int64
num int64
}
type result struct {
Obj *Obj
sum int64
}
var sw sync.WaitGroup
var Objnum = make(chan *Obj, 100)
var SumNum = make(chan *result, 100)
//生产者函数,随机数
func PD(a chan *Obj) {
var id int
defer sw.Done()
for {
id++
rand.Seed(time.Now().Unix())
x := rand.Int63()
new1 := &Obj{
id: int64(id),
num: x,
}
a <- new1
time.Sleep(time.Second)
}
}
//消费者函数
func consume(ch1 chan *Obj, SumNum chan *result) {
defer sw.Done()
for {
Obj := <-ch1
Sum := int64(0)
n := Obj.num
for n > 0 {
Sum += n % 10
n = n / 10
}
newResult := &result{
Obj: Obj,
sum: Sum,
}
SumNum <- newResult
}
}
func main() {
sw.Add(1)
go PD(Objnum)
sw.Add(24)
for i := 0; i < 24; i++ {
go consume(Objnum, SumNum)
}
for sum := range SumNum {
fmt.Printf("id号%d:,随机数:%d,和%d\n", sum.Obj.id,sum.Obj.num ,sum.sum)
}
sw.Wait()
}
第二种方式
package main
import (
"fmt"
"math/rand"
//"os"
"time"
)
//随机数通道
var itemChan chan *item
//求和管道
var resultChan chan *result
//随机数结构体
type item struct {
id int64
num int64
}
//求和结构体
type result struct {
item *item
sum int64
}
//生产者
func producer(ch chan *item) {
var id int64
for {
id++
//序列号自增
number := rand.Int63() //随机正整数
//随机数结构体封装
tmp := &item{
id: id,
num: number,
}
//随机数放入通道
ch <- tmp
// time.Sleep(time.Millisecond*100)
}
}
//求和运算
func calc(i int64) int64 {
//求和变量
var sum int64
for i > 0 {
//每位数字求和
sum = sum + i%10
i = i / 10
}
return sum
}
//消费者
func consumer(ch chan *item, retChan chan *result) {
for {
//从itemChan通道中取随机数结构体指针
tmp := <-ch
//数据运算
sum := calc(tmp.num)
//求和结构体封装
result := &result{
item: tmp,
sum: sum,
}
//求和数放入resultChan通道
retChan <- result
}
}
//输出遍历
func printResult(resultChan chan *result) {
for ret := range resultChan {
fmt.Printf("id:%v;num:%v;sum:%v\n", ret.item.id, ret.item.num, ret.sum)
//节奏输出控制
time.Sleep(time.Second)
}
}
//消费者goroutine数量控制
func startWorker(n int, ch chan *item, resultCh chan *result) {
//开启n数量的goroutine
for i := 0; i < n; i++ {
//goroutine调用函数
go consumer(ch, resultCh)
}
}
func main() {
//通道初始化,结构体指针类型
itemChan = make(chan *item, 100)
resultChan = make(chan *result, 100)
//启用生产者goroutine
go producer(itemChan)
//消费者goroutine,高并发处理
startWorker(100, itemChan, resultChan)
//调用输出函数
printResult(resultChan)
}
运行结果
他会一直执行下去
id:1;num:5577006791947779410;sum:95
id:2;num:8674665223082153551;sum:79
id:3;num:6129484611666145821;sum:81
id:4;num:4037200794235010051;sum:53
id:5;num:3916589616287113937;sum:95
id:6;num:6334824724549167320;sum:80
id:7;num:605394647632969758;sum:99
id:8;num:1443635317331776148;sum:77
id:9;num:894385949183117216;sum:89
id:10;num:2775422040480279449;sum:80
id:11;num:4751997750760398084;sum:99
id:12;num:7504504064263669287;sum:84
id:13;num:1976235410884491574;sum:88
id:14;num:3510942875414458836;sum:87
id:15;num:2933568871211445515;sum:80
id:16;num:4324745483838182873;sum:92
id:17;num:2610529275472644968;sum:89
....
使用通道只输出一万个值
package main
import (
"time"
"fmt"
"os"
"os/signal"
"sync"
)
var c chan os.Signal
var msgQueue chan *string
var wg sync.WaitGroup
func Producer(){
i := 0
LOOP:
for{
select {
case s := <-c:
fmt.Println()
fmt.Println("Producer | get", s)
break LOOP
default:
}
i ++
s := fmt.Sprintf("work-%d", i)
fmt.Println("Producer | produce", s)
msgQueue <- &s
time.Sleep(500 * time.Millisecond)
}
close(msgQueue)
fmt.Println("Producer | close channel, exit")
wg.Done()
}
func Consumer(){
for m := range msgQueue{
if m != nil{
fmt.Println("Consumer | consume", *m)
}else{
fmt.Println("Consumer | channel closed")
break
}
}
fmt.Println("Consumer | exit")
wg.Done()
}
func main(){
c = make(chan os.Signal, 1)
msgQueue = make(chan *string, 1000)
signal.Notify(c, os.Interrupt, os.Kill)
//pruducer
wg.Add(1)
go Producer()
//consumer
wg.Add(1)
go Consumer()
wg.Wait()
}
通道和goroutine配合处理指定数量数据
package main
import (
"fmt"
"math/rand"
)
//随机数通道
var itemChan chan *item
//求和管道
var resultChan chan *result
//随机数结构体
type item struct {
id int64
num int64
}
//空结构体通道
var doneChan chan struct{}
//求和结构体
type result struct {
item *item
sum int64
}
//生产者
func producer(itemCh chan *item) {
var id int64
//指定数量数据
for i := 0; i < 10000; i++ {
id++
//序列号自增
number := rand.Int63() //随机正整数
//随机数结构体封装
tmp := &item{
id: id,
num: number,
}
//随机数放入通道
itemCh <- tmp
// time.Sleep(time.Millisecond*100)
}
//itemChan通道关闭
close(itemCh)
}
//求和运算
func calc(i int64) int64 {
//求和变量
var sum int64
for i > 0 {
//每位数字求和
sum = sum + i%10
i = i / 10
}
return sum
}
//消费者
func consumer(ch chan *item, retChan chan *result) {
for tmp := range ch {
sum := calc(tmp.num)
//求和结构体封装
result := &result{
item: tmp,
sum: sum,
}
//求和数放入resultChan通道
retChan <- result
}
//传递空结构体进通道
doneChan <- struct{}{}
}
//输出遍历
func printResult(resultChan chan *result) {
for ret := range resultChan {
fmt.Printf("id:%v;num:%v;sum:%v\n", ret.item.id, ret.item.num, ret.sum)
//节奏输出控制
//time.Sleep(time.Second)
}
}
//消费者goroutine数量控制
func startWorker(n int, ch chan *item, resultCh chan *result) {
//开启n数量的goroutine
for i := 0; i < n; i++ {
//goroutine调用函数
go consumer(ch, resultCh)
}
}
//监控goroutine及时关闭通道
func closeChan(n int, doneChan chan struct{}, resultChan chan *result) {
//监控goroutine次数
for i := 0; i < n; i++ {
//取空结构体
<-doneChan
}
//计数通道关闭
close(doneChan)
//求和通道关闭
close(resultChan)
}
func main() {
//通道初始化,结构体指针类型
itemChan = make(chan *item, 10000)
resultChan = make(chan *result, 10000)
doneChan = make(chan struct{}, 30)
//启用生产者goroutine
go producer(itemChan)
//消费者goroutine,高并发处理
startWorker(30, itemChan, resultChan)
//启动gorountine监控流程是否结束
go closeChan(30, doneChan, resultChan)
//调用输出函数
printResult(resultChan)
}
只输出一万个,因为是并发,不按照顺序,其实到一万个数自动停止
...
id:7192;num:6762665259437917828;sum:103
id:7187;num:5802099863961749885;sum:107
id:7193;num:3024088589606596663;sum:94
id:7184;num:9012798112107155725;sum:73
id:7185;num:7445733868822300479;sum:90
id:7173;num:7990700824623006162;sum:72
id:9982;num:6793585068966742459;sum:109
id:9962;num:1266029665194304063;sum:73
id:7189;num:5551919529326822013;sum:78
id:7178;num:3895404382820407125;sum:75
id:9833;num:4616123227651317754;sum:73
id:7174;num:4240505806083476750;sum:74
id:7182;num:7571595319875649637;sum:107
id:7172;num:837347738337784340;sum:89
id:9953;num:5568458834365856656;sum:106
id:9844;num:3606295569004564335;sum:81
select多路复用
类似于用于通信的switch语句。每个case必须是一个通信操作,要么是发送要么是接收。
select 随机执行一个可运行的case。如果没有case 可运行,它将阻塞,直到有case可运行。一个默认的子句应该总是可运行的。
某些场景下,我们需要同事从多个通道接受数据,没有数据发生接收就会阻塞,我们往往可以使用一下方式解决。
语法示例
select {
case communication clause:
statement(s);
case communication clause :
statement(s);
/*你可以定义任意数量的case*/
default :/*可选*/
statement(s);
}
示例
·每个case都必须是一个通信·所有channel表达式都会被求值·所有被发送的表达式都会被求值
·如果任意某个通信可以进行,它就执行,其他被忽略。
·如果有多个case都可以运行,Select 会随机公平地选出一个执行。其他不会执行。
否则:
1.如果有default子句,则执行该语句。
2如果没有default子句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。
package main
import (
"fmt"
"math"
"time"
)
//定义两个通道
var ch1 = make(chan string, 100)
var ch2 = make(chan string, 100)
func sendK1(ch chan string) {
//产生数据
for i := 0; i < math.MaxInt64; i++ {
//向ch中发送数据
ch <- fmt.Sprintf("k1:%d", i)
//50毫秒放一个数据
time.Sleep(time.Millisecond * 50)
}
}
func sendK2(ch chan string) {
//产生数据
for i := 0; i < math.MaxInt64; i++ {
//向ch中发送数据
ch <- fmt.Sprintf("k2:%d", i)
//100毫秒放一个数据
time.Sleep(time.Millisecond * 100)
}
}
func main() {
go sendK1(ch1)
time.Sleep(time.Millisecond * 100)
go sendK2(ch2)
for {
/*如果通道都可以通信,随机公平执行其中一条,忽略其他,如果通道都不能通信,
且没有default语句,处于阻塞状态,直到可以通信为止*/
select {
case ret := <-ch1:
fmt.Println(ret)
case ret := <-ch2:
fmt.Println(ret)
default:
fmt.Println("没有其他数据可取")
time.Sleep(time.Millisecond * 500)
}
}
}
运行结果
PS D:\golang\src\day27\select\demo1> go run .\select.go
k1:0
k1:1
k2:0
没有其他数据可取
k2:1
k1:2
k1:3
k2:2
k2:3
k1:4
k1:5
k2:4
k1:6
k1:7
k1:8
k1:9
k1:10
没有其他数据可取
k1:11
k2:5
k1:12
k2:6
k2:7
k1:13
k1:14
k1:15
k1:16
k1:17
k1:18
k2:8
k2:9
没有其他数据可取
k1:19
k1:20
k1:21
k1:22
k1:23
k1:24
k2:10
k1:25
k2:11
k1:26
k2:12
k2:13
没有其他数据可取
k1:27
k2:14
k2:15
k1:28
k2:16
k1:29
k1:30
k1:31
k1:32
k1:33
k1:34
k2:17
k2:18
没有其他数据可取
k2:19
k1:35
k1:36
k2:20
k2:21
k1:37
k1:38
k2:22
k2:23
k1:39
k1:40
k1:41
k1:42
没有其他数据可取
k2:24
k1:43
k1:44
k2:25
k2:26
k2:27
k1:45
k1:46
k1:47
k1:48
k1:49
k1:50
没有其他数据可取
k1:51
k1:52
k1:53
k2:28
k2:29
k1:54
k1:55
k1:56
k2:30
k2:31
k1:57
k2:32
k1:58
没有其他数据可取
exit status 0xc000013a
ctrl+c停止
select可以解决死锁问题
package main
import (
"fmt"
)
func main() {
var ch = make(chan int, 1)
for i := 0; i < 10; i++ {
//可以解决死锁
select {
case ch <- i:
//fmt.Println("处理不了")
case ret := <-ch:
fmt.Println(ret)
}
}
}
运行结果
0
2
4
6
8
使用select完善生产者和消费者模型,键盘输入回车终止数据。
package main
import (
"fmt"
"math"
"os"
"time"
)
var b = make([]byte, 100)
//定义两个通道
var quit = make(chan string, 100)
var ch2 = make(chan string, 100)
var ch3 = make(chan string, 100)
func sendK1(ch chan string) {
//产生数据
for i := 0; i < math.MaxInt64; i++ {
//向ch中发送数据
ch <- fmt.Sprintf("k1:%d", i)
//50毫秒放一个数据
//time.Sleep(time.Millisecond * 50)
}
}
func sendK2(ch chan string) {
//产生数据
for i := 0; i < math.MaxInt64; i++ {
//向ch中发送数据
ch <- fmt.Sprintf("k2:%d", i)
//100毫秒放一个数据
//time.Sleep(time.Millisecond * 1000)
}
}
//回车退出
func sendK3(ch chan string) {
for i := 0; ; i++ {
fmt.Scanf("%c", &b[i])
fmt.Print(string(b[i]))
if b[i] == '\n' {
os.Exit(0)
ch <- string(b)
}
}
}
func main() {
go sendK1(quit)
time.Sleep(time.Millisecond * 100)
go sendK2(ch2)
go sendK3(ch3)
for {
/*如果通道都可以通信,随机公平执行其中一条,忽略其他,如果通道都不能通信,
且没有default语句,处于阻塞状态,直到可以通信为止*/
select {
case ret := <-quit:
fmt.Println(ret)
//return
case ret := <-ch2:
fmt.Println(ret)
case <-ch3:
default:
fmt.Println("没有其他数据可取")
//time.Sleep(time.Millisecond * 500)
//遇见回车退出
}
}
}
运行结果
回车可停
第二种方式
package main
import (
"fmt"
"math/rand"
"os"
)
//空格退出
//随机数通道
var itemChan chan *item
//求和管道
var resultChan chan *result
var exitChan chan struct{}
//随机数结构体
type item struct {
id int64
num int64
}
//求和结构体
type result struct {
item *item
sum int64
}
//生产者
func producer(itemCh chan *item) {
var id int64
//指定数量数据
for {
id++
//序列号自增
number := rand.Int63() //随机正整数
//随机数结构体封装
tmp := &item{
id: id,
num: number,
}
//随机数放入通道
itemCh <- tmp
// time.Sleep(time.Millisecond*100)
}
}
//求和运算
func calc(i int64) int64 {
//求和变量
var sum int64
for i > 0 {
//每位数字求和
sum = sum + i%10
i = i / 10
}
return sum
}
//消费者
func consumer(ch chan *item, retChan chan *result) {
//通知main计数-1
for tmp := range ch {
sum := calc(tmp.num)
//求和结构体封装
result := &result{
item: tmp,
sum: sum,
}
//求和数放入resultChan通道
retChan <- result
}
}
//输出遍历
func printResult(exitChan chan struct{}, resultChan chan *result) {
for {
//控制台控制数据开关
select {
//消费数据输出通道
case ret := <-resultChan:
fmt.Printf("id:%v,num:%v,sum:%v\n", ret.item.id, ret.item.num, ret.sum)
//time.Sleep(time.Second)
//控制台数据通道
case <-exitChan:
return
}
}
}
//消费者goroutine数量控制
func startWorker(n int, ch chan *item, resultCh chan *result) {
//开启n数量的goroutine
for i := 0; i < n; i++ {
//goroutine调用函数
go consumer(ch, resultCh)
}
}
//控制台输入
func inputer(exitChan chan struct{}) {
//字节数组
tmp := [1]byte{}
//从控制台输入
os.Stdin.Read(tmp[:])
//向exitChan中传入数据
exitChan <- struct{}{}
}
func main() {
//通道初始化,结构体指针类型
itemChan = make(chan *item, 10000)
resultChan = make(chan *result, 10000)
exitChan = make(chan struct{}, 1)
//启用生产者goroutine
go producer(itemChan)
//消费者goroutine,高并发处理
startWorker(30, itemChan, resultChan)
go inputer(exitChan)
//调用输出函数
printResult(exitChan, resultChan)
}
运行结果
这种方式回车不会立刻停止,因为信道里面还有数据没有全部取出来,他会在你回车的时候将剩下的数据释放出来,意义上实现了回车停止
id:5353,num:982834177255573862,sum:92
id:5354,num:7291943030372177817,sum:81
id:5356,num:3500252379460637325,sum:72
id:5345,num:7035598168391094882,sum:96
id:5357,num:3456275693262101224,sum:70
id:5358,num:5845323045055441055,sum:68
id:5359,num:6781058790380715290,sum:86
id:5360,num:774311384505828488,sum:86
id:5361,num:7973701242218090496,sum:81
单向通道
在函数中只能发送值不能接收值称之为只写通道,只能接收不能发送值称之为只读通道,让代码意向更明确,更清晰,
只写通道
package main
import "fmt"
//只写通道
func main() {
var ch chan<- int
for i := 0; i < 10; i++ {
ch <-i
}
ret := <-ch //会报错,语法错误
fmt.Println(ret)
}
只读通道
package main
import "fmt"
//只写通道
func main() {
var ch <-chan int
for i := 0; i < 10; i++ {
ch <-i //invalid operation: cannot send to receive-only type <-chan int
}
}
单向通道只在自己的函数生效
示例
package main
import "fmt"
var ch chan int
//只写通道
func warit(ch chan<- int) {
ch <- 10
}
//只读通道
func readch(ch <-chan int) int {
ret := <-ch
return ret
}
func main() {
ch = make(chan int, 1)
warit(ch)
//ret := <-ch
//fmt.Println(ret)
fmt.Println(readch(ch))
}
运行结果
10