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語言文章歡迎大家分享!
小白真不懂,大哥來照顧!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/229954.html
標籤:其他
上一篇:初識Java語言
