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

资源竞争

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

代码解析bash

// 这个示例程序展现如何在程序里形成竞争状态
// 实际上不但愿出现这种状况
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 语言中经过三种方式 处理竞争状态的状况markdown

锁住共享资源并发

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
复制代码

对于有缓冲和无缓冲的通道的区别能够看下面的这张图 详细地解释其中的差别性函数

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

有缓冲 的状况下 不须要双方同时链接上 只要有让发送者有空间能够存放 接收者有数据能够取出 就不会出现阻塞 spa

相关文章
相关标签/搜索