一. 基本用法
runtime
调度器是个非常有用的东西,关于 runtime
包几个方法:
- Gosched:让当前线程让出
cpu
以让其它线程运行,它不会挂起当前线程,因此当前线程未来会继续执行 - NumCPU:返回当前系统的
CPU
核数量 - GOMAXPROCS:设置最大的可同时使用的
CPU
核数 - Goexit:退出当前
goroutine
(但是defer
语句会照常执行) - NumGoroutine:返回正在执行和排队的任务总数
- GOOS:目标操作系统
二. 等待goroutine完成任务
- 创建一个 WaitGroup 实例,比如名称为:wg
- 调用 wg.Add(n),其中 n 是等待的 goroutine 的数量
- 在每个 goroutine 运行的函数中执行 defer wg.Done()
- 调用 wg.Wait() 阻塞主逻辑
package main
import (
"time"
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(2)
say2("hello", &wg)
say2("world", &wg)
wg.Wait()
fmt.Println("over!")
}
func say2(s string, waitGroup *sync.WaitGroup) {
defer waitGroup.Done()
for i := 0; i < 3; i++ {
fmt.Println(s)
}
}
输出
hello
hello
hello
world
world
world
over!
三. 锁
1. 原子锁
对于一个整数类型 T
, sync/atomic
标准库包提供了下列原子操作函数。 其中 T
可以是内置 int32
、 int64
、 uint32
、 uint64
和 uintptr
类型
func AddT(addr *T, delta T)(new T)
func LoadT(addr *T) (val T)
func StoreT(addr *T, val T)
func SwapT(addr *T, new T) (old T)
func CompareAndSwapT(addr *T, old, new T) (swapped bool)
sync/atomic
标准库包也提供了一个 Value
类型。以它为基的指针类型 *Value
拥有两个方法: Load
和 Store
。 Value
值用来原子读取和修改任何类型的Go值。
func (v *Value) Load() (x interface{})
func (v *Value) Store(x interface{})
注意点 01.
一旦 v.Store
方法( (v).Store
的简写形式)被曾经调用一次,则传递给值 v
的后续方法调用的实参的具体类型必须和传递给它的第一次调用的实参的具体类型一致; 否则,将产生一个恐慌。 nil
接口类型实参也将导致 v.Store()
方法调用产生恐慌。
比如:
package main
import (
"fmt"
"sync/atomic"
)
func main() {
type T struct{ a, b, c int }
var ta = T{1, 2, 3}
var v atomic.Value
v.Store(ta)
var tb = v.Load().(T)
fmt.Println(tb) // {1 2 3}
fmt.Println(ta == tb) // true
v.Store("hello") // 将导致一个恐慌
}
输出结果
goroutine 1 [running]:
sync/atomic.(*Value).Store(0x14000110210, {0x102f47900, 0x102f58038})
/opt/homebrew/Cellar/go@1.17/1.17.10/libexec/src/sync/atomic/value.go:77 +0xf4
main.main()
/Users/tomxiang/github/go-demo/hello/routine/routine07.go:17 +0x218
Process finished with the exit code 2
注意点02
一个 CompareAndSwapT
函数调用传递的旧值和目标值的当前值匹配的情况下才会将目标值改为新值,并返回 true
;否则立即返回 false
。
import (
"fmt"
"sync/atomic"
)
func main() {
type T struct{ a, b, c int }
var x = T{1, 2, 3}
var y = T{4, 5, 6}
var z = T{7, 8, 9}
var v atomic.Value
v.Store(x)
fmt.Println(v) // {{1 2 3}}
old := v.Swap(y)
fmt.Println("old:", old)
fmt.Println("v:", v) // {{4 5 6}}
fmt.Println("old.(T)", old.(T)) // {1 2 3}
swapped := v.CompareAndSwap(x, z)
fmt.Println(swapped, v) // false {{4 5 6}}
swapped = v.CompareAndSwap(y, z)
fmt.Println(swapped, v) // true {{7 8 9}}
}
输出结果
{{1 2 3}}
old: {1 2 3}
v: {{4 5 6}}
old.(T) {1 2 3}
false {{4 5 6}}
true {{7 8 9}}
2. 互斥锁
例1. Gosched
切换任务
mutex.Lock
// Package main 这个示例程序展示如何使用互斥锁来
// 定义一段需要同步访问的代码临界区
// 资源的同步访问
package main
import (
"fmt"
"runtime"
"sync"
)
var (
// counter是所有goroutine都要增加其值的变量
counter int
// wg用来等待程序结束
wg sync.WaitGroup
// mutex 用来定义一段代码临界区
mutex sync.Mutex
)
// main 是所有Go程序的入口
func main() {
// 计数加2,表示要等待两个goroutine
wg.Add(2)
// 创建两个goroutine
go incCounter(1)
go incCounter(2)
// 等待goroutine结束
wg.Wait()
fmt.Printf("Final Counter: %d\n", counter)
}
// 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
// 进入临界区
}
}
输出结果:
4
对 counter 变量的操作在第 46 行和第 60 行的 Lock() 和 Unlock() 函数调用定义的临界区里被保护起来。
同一时刻只有一个 goroutine 可以进入临界区。之后,直到调用 Unlock() 函数之后,其他 goroutine 才能进入临界区。当第 52 行强制将当前 goroutine 退出当前线程后,调度器会再次分配这个 goroutine 继续运行。当程序结束时,我们得到正确的值 4,竞争状态不再存在。
例2
- 在一个 goroutine 获得 Mutex 后,其他 goroutine 只能等到这个 goroutine 释放该 Mutex
- 使用 Lock() 加锁后,不能再继续对其加锁,直到利用 Unlock() 解锁后才能再加锁
- 在 Lock() 之前使用 Unlock() 会导致 panic 异常
- 已经锁定的 Mutex 并不与特定的 goroutine 相关联,这样可以利用一个 goroutine 对其加锁,再利用其他 goroutine 对其解锁
- 在同一个 goroutine 中的 Mutex 解锁之前再次进行加锁,会导致死锁
- 适用于读写不确定,并且只有一个读或者写的场景
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
func main() {
runtime.GOMAXPROCS(1)
var mutex sync.Mutex
wait := sync.WaitGroup{}
fmt.Println("Locked Start")
mutex.Lock()
for i := 1; i <= 5; i++ {
wait.Add(1)
go func(i int) {
fmt.Println("Not lock:", i)
mutex.Lock()
fmt.Println("Lock:", i)
time.Sleep(time.Second)
fmt.Println("Unlock:", i)
mutex.Unlock()
defer wait.Done()
}(i)
}
time.Sleep(time.Second)
fmt.Println("Unlocked finish")
mutex.Unlock()
wait.Wait()
}
输出结果
Locked Start
Not lock: 5
Not lock: 1
Not lock: 2
Not lock: 3
Not lock: 4
Unlocked finish
Lock: 5
Unlock: 5
Lock: 1
Unlock: 1
Lock: 2
Unlock: 2
Lock: 3
Unlock: 3
Lock: 4
Unlock: 4