概述

Golang 是一个跨平台的新生编程语言. 今天小白就带大家一起携手走进 Golang 的世界. (第 20 课)

【Golang】✔️走进 Go 语言✔️ 第二十课 原子操作 & 互斥体_i++

协程存在的问题

当大量的协程并发的时候, 会产生资源抢占冲突.

例子:

package main

import (
	"fmt"
	"time"
)

func main() {

	// 定义原子变量整数
	var ops uint64 = 0

	// 创建一坨协程
	for i := 0; i < 10; i++ {
		go func() {
			for j := 0; j < 10000; j++ {
			    // 线程冲突, 数据不精确
				ops += 1
			}
		}()
	}

	// 等待
	time.Sleep(time.Second * 5)

	// 调试输出
	fmt.Println(ops)

}

输出结果:

69980

我们可以看到, 输出的结果是 69980, 而非 100000, 原因是线程冲突导致数据不精确.

原子操作

atomic 提供的原子操作使得我们能够确保任一时刻只有一个协程 (go routine) 对变量进行操作. 善用 atomic 能够避免程序中出现大量的锁操作.

【Golang】✔️走进 Go 语言✔️ 第二十课 原子操作 & 互斥体_Go_02

例子:

package main

import (
	"fmt"
	"sync/atomic"
	"time"
)

func main() {

	// 定义原子变量整数
	var ops uint64 = 0

	//
	for i := 0; i < 10; i++ {
		go func() {
			for j := 0; j < 10000; j++ {
				// 避免线程冲突	
				atomic.AddUint64(&ops, 1)
			}
		}()
	}

	// 等待
	time.Sleep(time.Second * 5)

	// 调试输出
	fmt.Println(ops)

}

输出结果:

100000
互斥锁

互斥锁 (Mutex) 用于主动控制元素的变量在同一时间只能被一个协程访问.

Mutex 有两个方法:

  • func (*Mutex) Lock: Lock 方法锁住 m, 如果 m 已经加锁, 则阻塞到 m 解锁
  • func (*Mutex) Unlock: Unlock 方法解锁 m, 如果 m 未加锁会导致运行时错误

【Golang】✔️走进 Go 语言✔️ 第二十课 原子操作 & 互斥体_数据_03

例子:

package main

import (
	"fmt"
	"math/rand"
	"sync"
	"sync/atomic"
	"time"
)


func main() {
	var state = make(map[int]int)  // 状态
	var mutex = &sync.Mutex{}  // 互斥锁
	var readops uint64 = 0  // 读取数据
	var writeops uint64 = 0  // 写入数据


	// 循环读取
	for i := 0; i < 100; i++ {
		go func() {
			total := 0
			for {

				key := rand.Intn(5)
				mutex.Lock()  // 加锁
				total += state[key]  // 数据叠加
				mutex.Unlock()  // 解锁

				atomic.AddUint64(&readops, 1)  // 记录读取次数

				time.Sleep(time.Millisecond)
			}
		}()
	}

	// 循环写入
	for i := 0; i < 10; i++ {
		go func() {
			for {
				key := rand.Intn(5)
				val := rand.Intn(100)
				mutex.Lock()  // 加锁
				state[key] = val  // 写入数据
				mutex.Unlock()  // 解锁

				atomic.AddUint64(&writeops, 1)  // 写入次数
				time.Sleep(time.Millisecond)
			}
		}()
	}

	// 休眠1秒
	time.Sleep(time.Second * 1)

	// 调试输出
	read := atomic.LoadUint64(&readops)
	write := atomic.LoadUint64(&writeops)
	fmt.Println(read)
	fmt.Println(write)

	mutex.Lock()
	fmt.Println(state)
	mutex.Unlock()
	
}

输出结果:

5179
519
map[0:36 1:67 2:62 3:86 4:68]