关于go并发的实例及理解

GO的并发操作

为什么时隔已久我会再写文章呢?原来是佐天,有个学长问我,他说“小杨,我出个题考考你”。我说莫得问题,我来做。学长立马把题发上来了,啪的一下!很快啊!

题目是这样的:
现在有很多人(一个随机数),从中挑出来10个人让他们进行m轮游戏。是什么游戏呢?游戏是这样的,他们十个人在每轮游戏中都会报一个数,这个数是什么是随机的(不必纠结太多,用rand.Intn()就行),他们将这个数报告给裁判。裁判在每轮也会选一个数,如果某个人报的数字和裁判一样的话,那这个人就out了。现在要求你写一串小代码,要求不能使用全局变量,问m轮后还剩几人?

我一看题,嚯!这肯定要用我最近才学的go的并发来操作。只要创造十个协程,把东西发给裁判,如果不行就让这个人滚蛋。于是我写出了下面这段代码

package main

import (
	"fmt"
	"math/rand"
	"runtime"
	"time"
)

func main() {
	var m int

	fmt.Scanf("%d", &m)
	var sum int = 10
	for i := 0; i < m; i++ {

		ch1 := make(chan int, 10)
		for i := 1; i <= sum; i++ {
			go func() {
				num := rand.Intn(10)
				ch1 <- num
				runtime.Gosched()
			}()
		}

		time.Sleep(time.Second * 1)
		close(ch1)
		k := rand.Intn(10)
		for num := range ch1 {
			if num == k {
				sum--
				break
			}
		}
		fmt.Printf("%d", sum)
	}
}

我自信慢慢的把代码发给学长,脸上带着喜悦的笑容。欧耶!
可令我没想到的是,我的代码在逻辑上有些问题,因为我在暂停主协程的时候用的是time.sleep(),所以如果在我子协程完成前我就关闭channel了,那指定不行,选手不就无辜退赛了么!所以我就想到了使用sync包里面的waitgroup,来确保我协程运行的顺序没有问题。
以下代码是我的优化方案

package main

import (
	"fmt"
	"math/rand"
	"sync"
)

func main() {
	var m int
	fmt.Scanf("%d", &m)
	var wg sync.WaitGroup
	var sum int = 10
	for i := 0; i < m; i++ {
		ch1 := make(chan int, 10)
		//wg.Add(10)
		for i := 1; i <= sum; i++ {
			wg.Add(1)

			go func() {
				defer wg.Done()
				num := rand.Intn(10)
				ch1 <- num

			}()
		}

		wg.Wait()
		//time.Sleep(time.Second * 1)
		close(ch1)
		k := rand.Intn(10)
		for num := range ch1 {
			if num == k {
				sum--
				break
			}
		}
		fmt.Printf("%d\n", sum)
	}
}

这次总算是对了吧!我心里想着,把代码交给学长。
学长看到后,呃呃呃,你没有真正理解题的意思,可按照你这么想,你这个代码也写得过于复杂了,还是让我来吧!
果然大佬出手就知道有没有!以下是学长的代码

// 这个的物理意义是
// 进行M轮游戏
// 从无数个人中选n个人让他们随便报一个数
// 然后裁判判断是不是踩雷了
// 统计一下没有踩到雷的数量人
// 然后下一轮再找没有踩到雷数量的人进行一轮游戏
// 它并不是原先的n个人 因为他们的goroutine的地址都不一样
func game2() {
	ch := make(chan int)
	cnt := N
	for i := 0; i < M; i++ {
		myCnt := cnt
		for j := 0; j < myCnt; j++ {
			go func() {
				ch <- rand.Intn(MaxNumber)
			}()
		}
		num := rand.Intn(MaxNumber)
		for j := 0; j < myCnt; j++ {
			if val := <- ch; val == num {
				cnt--
			}
		}
	}
	fmt.Println(M, " 轮游戏还有",cnt,"个人")
}

看看,看看! 看看人家写的代码,这个是按照我们思路写的,写的比我们嗯嗯嗯嗯。。。。我就不说什么了,我有点害羞。(脸红脸红脸红)

可接下来看到的,才是这个题原本的样貌。以及我从这个题中所获得的许多新知识。

package main

import (
	"fmt"
	"math/rand"
	"sync"
)

func game1() {
	const (
		N         = 10 //10个人
		M         = 10 // 10轮游戏
		MaxNumber = 10
		gameOver  = 0
		gameNext  = 1
	)
	ch := make(chan chan int) //这里定义了一个管道类型的管道
	readyGame := sync.WaitGroup{}
	readyGame.Add(N)
	start := sync.WaitGroup{} //start是用来控制每轮游戏暂停,保证没有人先开下一轮的
	// N个人
	for i := 0; i < N; i++ {
		go func() {
			//每个人执行M轮游戏
			phone := make(chan int)
			for j := 0; j < M; j++ {
				// 告诉裁判我可以开始游戏了
				readyGame.Done()
				ch <- phone //注意这里是将phone给了ch,不是从phone里取值
				phone <- rand.Intn(MaxNumber)
				//裁判是通过ch里面的phone里面的val来判断你有没有出局
				if val := <-phone; val == gameOver {
					break
				}
				//这一轮游戏结束等待
				// 如果没有这一步的话 他会直接进入到写管道 这个时候他可能提前进入下一轮游戏 这样是不对的
				start.Wait() 
			}

		}()
	}
	//裁判
	start.Add(1)
	cnt := N
	for i := 0; i < M; i++ {
		num := rand.Intn(MaxNumber) //裁判选数字
		tempCnt := cnt
		for j := 0; j < tempCnt; j++ {
			phone := <-ch
			val := <-phone
			//fmt.Println(val)
			if num == val {
				phone <- gameOver
				cnt--
			} else {
				phone <- gameNext
			}
		}
		if i == M-1 {
			break
		}
		readyGame.Add(cnt) //给剩下的人发通行证
		start.Done()       //将裁判的1变为0
		readyGame.Wait()   //保证下一次开始前选手们都已经选好
		start.Add(1)
	}
	fmt.Println(M, " 轮游戏还有", cnt, "个人")
}

func main() {
	game1()
}

关于题目的问题我都已经放到代码的注释里面了。

总结:

1.在这次练习中,我感受到了go语言并发机制的魅力,以及极高的运行速度
2.在写代码的过程中我碰到了很多次死锁的问题,以后需要加强这方面的知识储备
3.对sync.waitgroup的理解更加深了一步,它可以用来调节主协程和子协程的运行顺序关系。

谢谢朋友们!如果有什么好的go语言文章欢迎大家分享!
小白真不懂,大哥来照顾!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值