0
点赞
收藏
分享

微信扫一扫

《7天学会Go语言并发编程》第一天 go并发编程介绍

蚁族的乐土 2022-04-14 阅读 66

目录

一、go语言并发简介

二、go语言并发小试牛刀

2.1 原生态并发计数

2.1 遇事不决,加把锁

2.2 加锁慢又麻,原子操作好快省

2.3 channel,go并发灵魂

三、总结


一、go语言并发简介

并发编程的目的主要利用多核CPU并发执行,提升程序计算性能。使用多核CPU带来了一个挑战就是,每一个CPU都有自己的缓存,CPU执行运算时,都是把数据先加载到自己的缓存中使用,加载过程中,或者计算过程中,其他CPU已经对这个变量进行了修改,导致自己没办法拿到最新的数据计算。

简单的概括就是:🚗使用并发编程时,不可避免的就是临界区(公共资源的修改),抽象的数据操作行为是RAW,WAR,WAW。🥤🥤🥤

go语言为其线程创造了一个独特名字:goroutine。所有goroutline都使用同一块内存,所以goroutine当中使用的数据都必须要加锁使用,否则就会出现数据冲突🦴🦴🦴。

这样:

二、🍻go语言并发小试牛刀

2.1🍺🍺🍺 原生态并发计数

  代码exam3实现的是并发开启十个线程计数,每个线程分别计数10次,正确的计数结果应该是1000次。

func exam1() {
	group := sync.WaitGroup{}
	count := 0
	for i := 0; i < 100; i++ {
		group.Add(1)
		go func() {
			for i := 0; i < 10; i++ {
				count++
			}
			group.Done()
		}()
	}
	group.Wait()
	fmt.Printf("100 goroutine count 10 times  is %v times", count)
}

实际结果却是:  990次

这就奇怪了,不应该呀。应该是1000的。


 

值对不上的原因就是:

 当go开启一个goroutine的时候,每一个goroutine都对count变量进行修改。👾没有加锁! 

原因分析:

下面就使用go -race检一下哪里有冲突。

哦豁,go -race检查以后,居然是一个正确的结果!!为啥呢

从下文前两行,可以看出0x00c00012e058 这个地址发生了RAW操作。虽然这个结果最后是1000次,这是操作系统自身缓存的原因优化出来的结果,属于运气。goroutine内部计数次数变化的话,count总值就不对了!

WARNING: DATA RACE
Read at 0x00c00012e058 by goroutine 8:
  main.exam1.func1()
       /MyOwnExercise.go:51 +0x4c

Previous write at 0x00c00012e058 by goroutine 7:
  main.exam1.func1()
       /MyOwnExercise.go:51 +0x64

Goroutine 8 (running) created at:
  main.exam1()
       /MyOwnExercise.go:49 +0xf3
  main.main()
       /MyOwnExercise.go:14 +0x36

Goroutine 7 (finished) created at:
  main.exam1()
       /MyOwnExercise.go:49 +0xf3
  main.main()
       /MyOwnExercise.go:14 +0x36
==================
100 goroutine count 10 times  is 1000 timesFound 1 data race(s)
exit status 66

2.1 🍭🍭🍭遇事不决,加把锁

加锁,使用mutex解决线程并发问题 。加锁通俗的说就是对资源上锁,拿到钥匙的人才能访问,控制住了钥匙,⚓️就能控制住访问顺序。

func exam2() {
	//增加锁
	mutex := sync.Mutex{}
	group := sync.WaitGroup{}
	count := 0
	for i := 0; i < 100; i++ {
		group.Add(1)
		go func( ) {
			//加锁
			mutex.Lock()
			for i := 0; i < 10000; i++ {
				count++
			}
			//解锁
			mutex.Unlock()
			group.Done()
		}()
	}
	group.Wait()
	fmt.Printf("100 goroutine count 1000 times  is %v times", count)
}

 执行结果:显然。正确答案,数据无冲突。

go run -race .\MyOwnExercise.go
100 goroutine count 1000 times  is 1000000 times

2.2 🍖🍖🍖加锁慢又麻,原子操作好快省

原理:就是每次对数据的操作,都不经过缓存,直接在数据地址上对数据进行操🌆作。

func atomicExam3() {
	group := sync.WaitGroup{}
	var count int32
	for i := 0; i < 100; i++ {
		group.Add(1)
		go func( ) {
			for i := 0; i < 10000; i++ {
				atomic.AddInt32(&count,1)
			}
			group.Done()
		}()
	}
	group.Wait()
	fmt.Printf("100 goroutine count 1000 times  is %v times", atomic.LoadInt32(&count))
}

 结论:那当然是正确了。博客里面怎么会有不对的答案

go run -race .\MyOwnExercise.go
100 goroutine count 1000 times  is 1000000 times

2.3 🍟🍟🍟channel,go并发灵魂

嗯,原理就是你发过来,我收着,就是一个半双工通信的模式。通过半双工🌠🌠,实现对修改数据的并发操作。

func channelExam3() {
	group := sync.WaitGroup{}
	resChann := make(chan int,1)
	resChann<-0
	for i := 0; i < 100; i++ {
		group.Add(1)
		go func( ) {
			for i := 0; i < 10000; i++ {
				i2 := <-resChann
				i2++
				resChann<-i2
			}
			group.Done()
		}()
	}
	group.Wait()
	fmt.Printf("100 goroutine count 1000 times  is %v times", <-resChann)
}

  执行结果:毫无意外,又是正确执行。

go run -race .\MyOwnExercise.go
100 goroutine count 1000 times  is 1000000 times

三、🍏🍏🍏总结

        今天介绍了一下,并发编程为什么会失败,为什么会有并发编程。如何通过加锁、原子操作、通道来分别实现并发次数。后续的文章中,会继续拓展、深化go并发编程原理和生产案例💪🏻⛽️~

举报

相关推荐

0 条评论