Go语言之sync包

Share Memory By Comunication

传统的线程模型(通常在编写 Java、C++ 和Python 程序时使用)程序员在线程之间通信需要使用共享内存。通常,共享数据结构由锁保护,线程都将争用这些锁来访问数据。在某些情况下,通过使用线程安全的数据结构(如 Python 的Queue),这会变得更容易。

Go 的并发原语 goroutines 和 channels 为构造并发软件提供了一种优雅而独特的方法。Go 没有显式地使用锁来协调对共享数据的访问,而是鼓励使用 chan 在 goroutine 之间传递对数据的引用。这种方法确保在给定的时间只有一个 goroutine 可以访问数据。

Do not communicate by sharing memory;instead, share memory by communicating.

Demo:

// 互斥锁方式:
type Resource struct {
   
	url        string
	polling    bool  // is polling?
	lastPolled int64 // lastpolled time
}

type Resources struct {
   
	data []*Resource
	lock *sync.Mutex
}

// Poller can be visited by goroutines
func Poller(res *Resources) {
   
	for {
   
		// get the last recently-polled Resource and mark it as being polled
		res.lock.Lock()
		var r *Resource
		for _, v := range res.data {
   
			if v.polling {
   
				continue // we continue code when polling
			}
			if r == nil || v.lastPolled < r.lastPolled {
   
				r = v
			}
		}

		// change status
		if r != nil {
   
			r.polling = true
		}

		// do sth...

		res.lock.Unlock()
		if r == nil {
   
			continue
		}

		// poll the URL
		// update the Resource's polling and lastpolled
		res.lock.Lock()
		r.polling = false
		u, _ := time.ParseDuration("1ns")
		r.lastPolled = u.Nanoseconds()
		res.lock.Unlock()
	}
}
// channel 方式
type Resource string

// in为输入,out为输出的 chan
func Poller(in, out chan *Resource) {
   
	// 循环取获取这个输入
	for r := range in {
   
		// poll the URL

		// send the processed Resource to out
		out <- r // 将输入处理后的放入另一个chan
	}
}

Detecting Race Conditions With Go

**data race 是两个或多个 goroutine 访问同一个资源(如变量或数据结构),并尝试对该资源进行读写而不考虑其他 goroutine。**这种类型的代码可以创建您见过的最疯狂和最随机的 bug。通常需要大量的日志记录和运气才能找到这些类型的bug。

早在Go 1.1中,Go 工具引入了一个 race detector。竞争检测器是在构建过程中内置到程序中的代码。然后,一旦你的程序运行,它就能够检测并报告它发现的任何竞争条件。它非常酷,并且在识别罪魁祸首的代码方面做了令人难以置信的工作。

go build -race

go test -race

package main

import (
	"fmt"
	"sync"
)

var Wait sync.WaitGroup
var Counter int = 0

func main() {
   
	for routine := 1; routine <= 2; routine++ {
   
		Wait.Add(1)
		go Routine(routine)
	}

	Wait.Wait()
	fmt.Printf("Final Counter: %d\n", Counter)
}

func Routine(id int) {
   
	for cout := 0; cout < 2; cout++ {
   
        	// time.Sleep(1 * time.Nanosecond)
		value := Counter
		value++ 
		Counter = value
	}
	Wait.Done()
}

>go build -race main.go
>main.exe
==================
WARNING: DATA RACE
Read at 0x000000f64608 by goroutine 8:		// 进行了 read 行为
  main.Routine()
      D:/code/go/test/main.go:23 +0x4e

Previous write at 0x000000f64608 by goroutine 7:	// 进行了 write 行为
  main.Routine()
      D:/code/go/test/main.go:26 +0x6a

Goroutine 8 (running) created at:
  main.main()
      D:/code/go/test/main.go:14 +0x7c

Goroutine 7 (finished) created at:
  main.main()
      D:/code/go/test/main.go:14 +0x7c
==================
Final Counter: 4		// 理论上结果应该为4
Found 1 data race(s)	// 发现开了多个goroutine,一个goroutine同时产生了读写行为,发现data 						// race,会将整个进程退掉

工具似乎检测到代码的争用条件。如果您查看race condition 报告下面,您可以看到程序的输出: 全局计数器变量的值为 2 或者 4。

等于4:我们启动了两个 goroutine,一个 goroutine 在赋值结束后 Counter 为2,这时另一个 goroutine 启动,读取到的 Counter 为2,所以最终结果为4。

等于2:当启动一个 goroutine 的时候,还未对 Counter 进行赋值,另一个 goroutine 也启动了,这时两个 Counter 读取的都是0,所以最终结果就是2。

我们将上面代码的 Routine 中 sleep 解注释,看看有什么区别:

==================
WARNING: DATA RACE
Write at 0x000000e65608 by goroutine 8:
  main.Routine()
      D:/code/go/test/main.go:27 +0x77

Previous read at 0x000000e65608 by goroutine 7:
  main.Routine()
      D:/code/go/test/main.go:24 +0x4e

Goroutine 8 (running) created at:
  main.main()
      D:/code/go/test/main.go:15 +0x7c

Goroutine 7 (running) created at:
  main.main()
      D:/code/go/test/main.go:15 +0x7c
==================
==================
WARNING: DATA RACE
Write at 0x000000e65608 by goroutine 7:
  main.Routine()
      D:/code/go/test/main.go:27 +0x77

Previous read at 0x000000e65608 by goroutine 8:
  main.Routine()
      D:/code/go/test/main.go:24 +0x4e

Goroutine 7 (running) created at:
  main.main()
      D:/code/go/test/main.go:15 +0x7c

Goroutine 8 (running) created at:
  main.main()
      D:/code/go/test/main.go:15 +0x7c
==================
Final Counter: 2			// 结果变为了2
Found 2 data race(s)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zyWRKMez-1619626486074)(E:\MyFile\GO语言进阶训练营\第03周并行编程\goroutine争资源图.png)]

试图通过 i++ 方式来解决原子赋值的问题,但是我们通过查看底层汇编:

0064 (./main.go
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值