Go 资源竞争的解决方案 (1. 原子函数 / 互斥锁 2. 通道)

资源竞争

如果多个goroutine在没有互相同步的情况,访问某个共享的资源, 并试图同时读和写这个资源,就处于相互竞争的状态, 这种情况被称作竞争状态(race candition)。 竞争状态的存在是让并发程序变得复杂的地方,十分容易引起潜在问题。对一个共享资源的读和写操作必 须是原子化的。(即同一时刻只能有一个 goroutine 对共享资源进行读和写操作)

代码解析

// 这个示例程序展示如何在程序里造成竞争状态
// 实际上不希望出现这种情况
package main

import (
	"fmt"
	"runtime"
	"sync"
)
var (
	counter int             // counter 是所有 goroutine 都要增加其值的变量
	wg sync.WaitGroup       // wg 用来等待程序结束
)

// main 是所有 Go 程序的入口
func main() {
	// 计数加 ,表示要等待两个 goroutine
	wg.Add()
	// 创建两个 goroutine
	go incCounter()
	go incCounter()
	// 等待 goroutine 结束
	wg.Wait()
	fmt.Println("Final Counter:", counter)
}

// incCounter 增加包里 counter 变量的值
func incCounter(id int) {
	// 在函数退出时调用 Done 来通知 main 函数工作已经完成
	defer wg.Done()

	for count :=; count <; count++ {
		// 捕获 counter 的值
		value := counter

		// 当前 goroutine 从线程退出,并放回到队列
		runtime.Gosched()

		// 增加本地 value 变量的值
		value++

		// 将该值保存回 counter
		counter = value
	}
}

/*
    在这段代码中
    变量counter会进行4次读和写操作,每个goroutine执行两次。
    但是,程序终止时,counter变量的值为 2。
    下图 提供了为什么会这样的线索。
    每个 goroutine 都会覆盖另一个 goroutine 的工作。
    这种覆盖发生在 goroutine 切换的时候。
    每个 goroutine 创造了一个 counter 变量的副本,
    之后就切换到另一个 goroutine。当这个 goroutine再次运行的时候,
    counter 变量的值已经改变了,但是 goroutine 并没有更新自己的那个副本的值,
    而是继续使用这个副本的值,用这个值递增,并存回 counter 变量
    结果覆盖了另一个goroutine 完成的工作。
*/
复制代码

Go 语言中通过三种方式 处理竞争状态的情况

锁住共享资源

1. 原子函数

使用atmoic 包内的相关函数执行原子操作 对数据进行安全的读和写 
保证数据不会出现覆的情况 
atomic.LoadInt64(&shutdown)        // 同步获取该数据的值 
atomic.StoreInt64(&shutdown, 1);   // 同步写入该数据的值
复制代码

2. 互斥锁

/*
    通过 mutex.Lock() mutex.Unlock() 划分临界区
    被包裹在临界区内的代码 同一个时间点只能被一个goroutine 执行
*/

// incCounter 使用互斥锁来同步并保证安全访问,
// 增加包里 counter 变量的值

func incCounter(id int) {
	// 在函数退出时调用 Done 来通知 main 函数工作已经完成
	defer wg.Done()
	for count := 0; count < 2; count++ {
		// 同一时刻只允许一个 goroutine 进入
		// 这个临界区
		mutex.Lock()     // <<<<<<<<<<<<<<<<<<<<< 互斥锁开始语句
		{
			// 捕获 counter 的值
			value := counter
			// 当前 goroutine 从线程退出,并放回到队列
			runtime.Gosched()
			// 增加本地 value 变量的值
			value++
			// 将该值保存回 counter
			counter = value
		}
		mutex.Unlock()   // <<<<<<<<<<<<<<<<<<<<< 互斥锁结束语句
		// 释放锁,允许其他正在等待的 goroutine
		// 进入临界区
	}
}

复制代码

3. 通道

通过使用通道发送和接收需要共享的资源,可以在goroutine之间做数据同步。
当一个资源需要在 goroutine 之间共享时,通道在 goroutine 之间架起了一个管道,
并提供了确保同步交换数据的机制。声明通道时,需要指定将要被共享的数据的类型。
可以通过通道共享内置类型、命名类型、结构类型和引用类型的值或者指针。
复制代码
// 无缓冲的整型通道
unbuffered := make(chan int)
// 有缓冲的字符串通道
buffered := make(chan string, 10)


// 通过通道发送一个字符串
buffered <- "Gopher"
// 从通道接收一个字符串
value := <-buffered
复制代码

对于有缓冲和无缓冲的通道的区别可以看下面的这张图 详细地解释其中的差异性

无缓冲 的情况下 需要接送方与发送方 同时连接上. 若一方不存在 则会处于阻塞状态

有缓冲 的情况下 不需要双方同时连接上 只要有让发送者有空间可以存放 接收者有数据可以取出 就不会出现阻塞

转载于:https://juejin.im/post/5c77aec0f265da2d8e70fbc8

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值