go 关键字用来开启一个协程
package main
import "time"
type myDate struct {
count int
}
func (d *myDate) add() {
d.count++
}
func (d *myDate) read() {
println(d.count)
}
func main() {
d := myDate{}
for i := 0; i < 1000; i++ {
// 开启1000个协程分别对count加1
go d.add()
}
// 主协程等待3秒,让1000个子协程执行完
time.Sleep(3 * time.Second)
d.read()//打印结果986与预期不一致
}
sync.WaitGroup
让主协程睡眠等待有所不妥,可以使用sync.WaitGroup。sync.WaitGroup有三个方法: Add()设置等待值、 Done()等待值减一、 Wait()检查等待值是否为0,为0立即返回,否则阻塞当前协程。
package main
import (
"sync"
)
type myDate struct {
count int
}
// 传入指针类型否则将是一个新的wg
func (d *myDate) add(wg *sync.WaitGroup) {
d.count++
// 等待值减一
wg.Done()
}
func (d *myDate) read() {
println(d.count)
}
func main() {
var wg sync.WaitGroup
// 设置等待值1000
wg.Add(1000)
d := myDate{}
for i := 0; i < 1000; i++ {
// 开启1000个协程分别对count加1
go d.add(&wg)
}
// 主协程检查等待值是否为0(1000个子协程执行完),如果是0则继续往下执行,否则阻塞当前协程
wg.Wait()
d.read() //打印结果986与预期不一致
}
sync.Mutex
互斥锁的零值表示未上锁。为防止出现异常现象上锁、解锁需成对出现。互斥锁的重复上锁操作会阻塞当前协程。互斥锁的重复解锁会出现恐慌。解锁一个未上锁的写锁会引起恐慌,解锁一个未上锁的读锁不会引起恐慌。
package main
import (
"sync"
)
type myDate struct {
count int
sync.Mutex
}
func (d *myDate) add(wg *sync.WaitGroup) {
d.Lock() //上锁
defer d.Unlock() //解锁
d.count++
wg.Done()
}
func (d *myDate) read() {
d.Lock() //上锁
defer d.Unlock() //解锁
println(d.count)
}
func main() {
var wg sync.WaitGroup
// 设置等待值1000
wg.Add(1000)
d := myDate{}
for i := 0; i < 1000; i++ {
// 开启1000个协程分别对count加1
go d.add(&wg)
}
// 主协程检查等待值是否为0(1000个子协程执行完),如果是0则继续往下执行,否则阻塞当前协程
wg.Wait()
d.read() //打印1000
}
sync.RWMutex
读写锁可以分别对读、写操作进行上锁解锁。被读锁锁定的资源允许同时有多个读操作,但同时只能有一个写操作。
package main
import (
"sync"
)
type myDate struct {
count int
sync.RWMutex
}
func (d *myDate) add(wg *sync.WaitGroup) {
d.Lock() //写操作上锁
defer d.Unlock() //写操作解锁
d.count++
wg.Done()// 等待值减一
}
func (d *myDate) read(wg *sync.WaitGroup) {
d.RLock() //读操作上锁
defer d.RUnlock() //读操作解锁
println(d.count)
wg.Done()// 等待值减一
}
func main() {
var wg sync.WaitGroup
// 设置等待值1010
wg.Add(1010)
d := myDate{}
// 开启1000个协程分别对count加1
for i := 0; i < 1000; i++ {
go d.add(&wg)
}
for i := 0; i < 10; i++ {
go d.read(&wg)
}
// 主协程检查等待值是否为0(1000个子协程执行完),如果是0则继续往下执行,否则阻塞当前协程
wg.Wait()
}
sync/atomic
原子操作是由一个独立的CPU指令完成的,因此可以在并发环境下保证操作数据的安全。
package main
import (
"sync"
"sync/atomic"
)
type myDate struct {
count int64
}
func (d *myDate) add(wg *sync.WaitGroup) {
//d.count 原子加1
atomic.AddInt64(&d.count, 1)
// 等待值减一
wg.Done()
}
func (d *myDate) read() {
println(d.count)
}
func main() {
var wg sync.WaitGroup
wg.Add(1000)
d := myDate{}
for i := 0; i < 1000; i++ {
go d.add(&wg)
}
wg.Wait()
d.read()打印1000
}
sync.Once
sync.Once只有一个Do方法,该方法只会被执行一次
package main
import (
"sync"
)
func main() {
var one sync.Once
var wg sync.WaitGroup
count := 10
wg.Add(count)
for i := 0; i < 10; i++ {
one.Do(func() {
for j := 0; j < count; j++ {
go func(i, j int) {
// i值始终为0
println(i, j)
// 等待值减一
wg.Done()
}(i, j)
}
})
}
one.Do(func() {
println("不打印")
})
wg.Wait()
}
sync.Pool
数据存储池,get获取的值是个不确定的值,有可能已被垃圾回收器回收。当获取不到数据时会调用New函数,如果没有New函数返回nil。
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
wgcount := 10
wg.Add(wgcount)
var pool sync.Pool
//向池中加入数据
pool.Put(1)
pool.Put(2)
pool.Put(3)
//返回并冲pool中删除数据
fmt.Println(pool.Get())//1
fmt.Println(pool.Get())//3
fmt.Println(pool.Get())//2
fmt.Println(pool.Get())//<nil>
fmt.Println(pool.Get())//<nil>
type user struct {
Name string
Age int
}
u := user{Name: "tome", Age: 18}
//向池中加入数据
pool.Put(&u)
//没有获取到数据时调用此函数
pool.New = func() any {
println("没有获取到数据时调用此函数")
return &u
}
for i := 0; i < wgcount; i++ {
go func() {
a := pool.Get()
if u1, ok := a.(*user); ok {
u1.Age++
pool.Put(&u1)
fmt.Println(u1)
}
wg.Done()
}()
}
wg.Wait()
fmt.Println(u)
}
sync.Map
并发安全的map
package main
import (
"fmt"
"sync"
)
func main() {
var pool sync.Map
type user struct {
Name string
Age int
}
//保存数据
pool.Store("tom", user{Name: "tome", Age: 18})
pool.Store("tim", user{Name: "tim", Age: 19})
// 获取数据
if u, ok := pool.Load("tom"); ok {
fmt.Println(u)
} else {
println("数据不存在")
}
if u, ok := pool.Load("jim"); ok {
fmt.Println(u)
} else {
println("数据不存在")
}
// 覆盖存量数据
pool.Store("tim", user{Name: "tim", Age: 20})
if u, ok := pool.Load("tim"); ok {
fmt.Println(u)
} else {
println("数据不存在")
}
//删除数据
pool.Delete("tim")
if u, ok := pool.Load("tim"); ok {
fmt.Println(u)
} else {
println("数据不存在")
}
actual, loaded := pool.LoadOrStore("timdd", user{Name: "tiddm", Age: 22})
fmt.Println(actual, loaded)
// 遍历pool
pool.Range(func(key, value any) bool {
fmt.Println(key, value)
return false
})
}
chan
chan通道,用于协程间的数据传送。 golang学习之chan
package main
import (
"fmt"
)
// 生产者
func Producer(queue chan<- int) {
for i := 0; i < 10; i++ {
go func(i int) {
queue <- i
}(i)
}
}
// 消费者
func Consumer(queue <-chan int) {
for i := 0; i < 10; i++ {
v := <-queue
fmt.Println("消费", v)
}
}
func main() {
queue := make(chan int)
Producer(queue)
Consumer(queue)
}
sync.Cond
通过某个条件变量控制多个协程之间的协作运行。模拟实现sync.WaitGroup 主协程等待子协程完成工作:
package main
import (
"fmt"
"sync"
)
func main() {'
cond := sync.NewCond(&sync.Mutex{})
count := 10
adder := 0
for i := 0; i < count; i++ {
go func(i int) {
fmt.Println("子goroutine", i, "do something")
cond.L.Lock()
adder++
cond.L.Unlock()
cond.Broadcast()
}(i)
}
cond.L.Lock()
for adder != count {
fmt.Println("主goroutine waiting")
cond.Wait()
}
cond.L.Unlock()
fmt.Println("done")
}
context
context可用于父子协程之间务完成情况同步信息。
package main
import (
"context"
"fmt"
"time"
)
func main() {
t := 2
// 根context
ctx := context.Background()
// 生成一个超时context
ctx, cancel := context.WithTimeout(ctx, time.Duration(t)*time.Second)
defer cancel()
go func() {
// 模拟子协程工作3秒
time.Sleep(3 * time.Second)
cancel()
}()
select {
case <-ctx.Done():// 任务已结束
fmt.Println("testWTimeout.Done:", ctx.Err())// 任务结束原因
case e := <-time.After(time.Duration(t) * time.Second):// 任务超时
fmt.Println("testWTimeout:", e)
}
}