go并发日记·多goroutine并发安全的操作共享变量

筚路蓝缕,以启山林。抚有蛮夷,以属华夏。不鸣则已,一鸣惊人。
                                                                                                          ——《左传`宣公十二年》

 

本文的完成完全是兴趣所致,于指尖之间写出能实现自己想法的代码实在是一件有趣的事情。

如果你有不同的想法或更好的办法欢迎一起交流,若有不正之处欢迎指正,共同进步!       用代码说话:

目录

基本实现

高并发背景下实现


基本实现

把锁玩起来


// 多个协程并发安全的操作共享变量:多个goroutine来操作变量,但在同一个时刻最多只有一个goroutine在操作共享变量。
// 背景设计:三个人共同抢票,抢完一次(只要还能抢到)还可继续抢,直到没票。
var tickets = 15 // 剩余15张票
var lock sync.Mutex
var wait sync.WaitGroup
var countA, countB, countC int

func buyTickets(name string) bool {
	// 每个人抢完一次后我都要打印剩余票数,因此采用互斥锁。如果用读写锁则打印出来的票数会不正确(加锁时其他协程依旧可读)
	lock.Lock()
	defer lock.Unlock()
	if tickets <= 0 {
		return true
	}
	tickets--
	if name == "A" {
		countA++
	}
	if name == "B" {
		countB++
	}
	if name == "C" {
		countC++
	}
	fmt.Printf("%s 抢到一张票,剩余:%v 张. \n", name, tickets)
	return false
}

func workM(w *sync.WaitGroup) {
	str := []string{"A", "B", "C"}
	for i := range str {
		go func(name string) {
			defer w.Done()
			for {
				if res := buyTickets(name); res {
					fmt.Println(name, ":没票了,结束。")
					break
				}
				r := rand.Intn(20)
				time.Sleep(time.Millisecond * time.Duration(r)) //模拟不同的人不同的网络延迟
			}
		}(str[i])
	}
}

func main() {
	wait.Add(3)
	workM(&wait) // 3个人抢票
	wait.Wait()
	defer func() {
		fmt.Printf("抢票结束。A抢到%d个;B抢到%d个;C抢到%d个。\n", countA, countB, countC)
		fmt.Println("当前运行的goroutine: ", runtime.NumGoroutine()) // 1,打印到这句时,除main外所有goroutine全部结束
	}()
}

控制台

C 抢到一张票,剩余:14 张.
A 抢到一张票,剩余:13 张.
B 抢到一张票,剩余:12 张.
C 抢到一张票,剩余:11 张.
B 抢到一张票,剩余:10 张.
A 抢到一张票,剩余:9 张.
B 抢到一张票,剩余:8 张.
B 抢到一张票,剩余:7 张.
B 抢到一张票,剩余:6 张.
C 抢到一张票,剩余:5 张.
C 抢到一张票,剩余:4 张.
A 抢到一张票,剩余:3 张.
B 抢到一张票,剩余:2 张.
B 抢到一张票,剩余:1 张.
C 抢到一张票,剩余:0 张.
A :没票了,结束。
B :没票了,结束。
C :没票了,结束。
抢票结束。A抢到3个;B抢到7个;C抢到5个。
当前运行的goroutine:  1

Process finished with exit code 0

当并发量大时呢?,一瞬间突增大量goroutine,虽然goroutine较为轻量,但大量的G势必给CPU使用和内存造成一定压力,这里我以增加有缓冲通道的办法,来限制并发量大时并发执行的goroutine数量,高并发时改造一下看看

高并发背景下实现


// 设置60个人抢20张票
var tickets = 20
var lock sync.Mutex
var wait sync.WaitGroup
var midCh chan struct{}
var personList []string
var personCountMap map[string]int

// 初始化每个人手里的余票数
func initCount() {
	// 用来测试,随便设置几个人都可以
	personList = append(personList, "A1", "B2", "C3", "D4", "E5", "F6", "G7", "H8", "I9", "J10",
		"A11", "B12", "C13", "D14", "E15", "F16", "G17", "H18", "I19", "J20",
		"A21", "B22", "C23", "D24", "E25", "F26", "G27", "H28", "I29", "J30",
		"A31", "B32", "C33", "D34", "E35", "F36", "G37", "H38", "I39", "J40",
		"A41", "B42", "C43", "D44", "E45", "F46", "G47", "H48", "I49", "J50",
		"A51", "B52", "C53", "D54", "E55", "F56", "G57", "H58", "I59", "J60")
	for i := range personList {
		personCountMap[personList[i]] = 0
	}
}

func buyTickets(name string, midCh chan struct{}) bool {

	lock.Lock()
	defer lock.Unlock()
	if tickets <= 0 {
		return true
	}
	tickets--
	if oldCount, ok := personCountMap[name]; ok {
		if oldCount == 3 { // 每人最多限购3份
			return true
		}
		personCountMap[name] = oldCount + 1
	}

	fmt.Printf("%s 抢到了一张票,剩余:%v 张. \n", name, tickets)

	return false
}

func workM(w *sync.WaitGroup, midCh chan struct{}, str []string) {
	for i := range str {
		midCh <- struct{}{} // 达到缓冲值且前面的goroutine没有执行完时此处短暂阻塞
		w.Add(1)
		go func(name string) {
			defer w.Done()
			for {
				r1 := rand.Intn(200)
				r2 := rand.Intn(1800)
				time.Sleep(time.Millisecond * time.Duration(r2-r1))
				if res := buyTickets(name, midCh); res {
					fmt.Println(name, ":呦,没的抢了,溜了。")
					<-midCh
					return
				}
			}
		}(str[i])
	}
}

func main() {
	midCh = make(chan struct{}, 20)
	personCountMap = make(map[string]int)
	initCount()
	fmt.Println("初始化完成。")
	workM(&wait, midCh, personList) // 很多人抢
	wait.Wait()
	defer func() {
		fmt.Println("----------抢票结束,统计一下----------")
		for k, v := range personCountMap {
			if v != 0 {
				fmt.Printf(" %s 抢到 %d个。\n", k, v)
			}
		}
		println("当前运行的goroutine: ", runtime.NumGoroutine())
	}()
}

看看控制台:

初始化完成。
E5 抢到了一张票,剩余:19 张. 
C13 抢到了一张票,剩余:18 张. 
G17 抢到了一张票,剩余:17 张. 
A1 抢到了一张票,剩余:16 张. 
C3 抢到了一张票,剩余:15 张. 
H18 抢到了一张票,剩余:14 张. 
H8 抢到了一张票,剩余:13 张. 
E5 抢到了一张票,剩余:12 张. 
G17 抢到了一张票,剩余:11 张. 
G17 抢到了一张票,剩余:10 张. 
F16 抢到了一张票,剩余:9 张. 
F16 抢到了一张票,剩余:8 张. 
I19 抢到了一张票,剩余:7 张. 
H8 抢到了一张票,剩余:6 张. 
H18 抢到了一张票,剩余:5 张. 
J10 抢到了一张票,剩余:4 张. 
F6 抢到了一张票,剩余:3 张. 
F16 抢到了一张票,剩余:2 张. 
E5 抢到了一张票,剩余:1 张. 
B2 抢到了一张票,剩余:0 张. 
D4 :呦,没的抢了,溜了。
D14 :呦,没的抢了,溜了。
F16 :呦,没的抢了,溜了。
A1 :呦,没的抢了,溜了。
C23 :呦,没的抢了,溜了。
E25 :呦,没的抢了,溜了。
E15 :呦,没的抢了,溜了。
B12 :呦,没的抢了,溜了。
H28 :呦,没的抢了,溜了。
J20 :呦,没的抢了,溜了。
C3 :呦,没的抢了,溜了。
C13 :呦,没的抢了,溜了。
G27 :呦,没的抢了,溜了。
C33 :呦,没的抢了,溜了。
B22 :呦,没的抢了,溜了。
A11 :呦,没的抢了,溜了。
F36 :呦,没的抢了,溜了。
D24 :呦,没的抢了,溜了。
I9 :呦,没的抢了,溜了。
I19 :呦,没的抢了,溜了。
G17 :呦,没的抢了,溜了。
H8 :呦,没的抢了,溜了。
G7 :呦,没的抢了,溜了。
G37 :呦,没的抢了,溜了。
H18 :呦,没的抢了,溜了。
J30 :呦,没的抢了,溜了。
F6 :呦,没的抢了,溜了。
B42 :呦,没的抢了,溜了。
E35 :呦,没的抢了,溜了。
D44 :呦,没的抢了,溜了。
I39 :呦,没的抢了,溜了。
A51 :呦,没的抢了,溜了。
B52 :呦,没的抢了,溜了。
J10 :呦,没的抢了,溜了。
B32 :呦,没的抢了,溜了。
F26 :呦,没的抢了,溜了。
F46 :呦,没的抢了,溜了。
G47 :呦,没的抢了,溜了。
B2 :呦,没的抢了,溜了。
D34 :呦,没的抢了,溜了。
H38 :呦,没的抢了,溜了。
A31 :呦,没的抢了,溜了。
E55 :呦,没的抢了,溜了。
C43 :呦,没的抢了,溜了。
I29 :呦,没的抢了,溜了。
H48 :呦,没的抢了,溜了。
A41 :呦,没的抢了,溜了。
E5 :呦,没的抢了,溜了。
A21 :呦,没的抢了,溜了。
I59 :呦,没的抢了,溜了。
I49 :呦,没的抢了,溜了。
J40 :呦,没的抢了,溜了。
H58 :呦,没的抢了,溜了。
D54 :呦,没的抢了,溜了。
J50 :呦,没的抢了,溜了。
C53 :呦,没的抢了,溜了。
E45 :呦,没的抢了,溜了。
J60 :呦,没的抢了,溜了。
F56 :呦,没的抢了,溜了。
G57 :呦,没的抢了,溜了。
----------抢票结束,统计一下----------
 B2 抢到 1个。
 H18 抢到 2个。
 F16 抢到 3个。
 E5 抢到 3个。
 G17 抢到 3个。
 C3 抢到 1个。
 H8 抢到 2个。
 J10 抢到 1个。
 F6 抢到 1个。
 I19 抢到 1个。
 A1 抢到 1个。
 C13 抢到 1个。
当前运行的goroutine:  1

Process finished with exit code 0

 

 

 

 

 

你可能感兴趣的:(#,go)