并发与并行
- 并发
同一时间段内执行多任务,你和两人聊天,具体到某一个人的时候需要切换聊天窗口
- 并行
goroutine
goroutine 类似线程.,由runtime调度和管理
- 在linux 内核中,实际上线程也是”进程”,goroutine 是用户态线程
- 其分配栈空间大小在开始时一般只有2kb,其大小会随着程序运行自动扩容或者缩小,而os
使用-go
func main() {
for i := 0; i < 100; i++ {
go func(i int) {
fmt.Println(i)
}(i)
}
fmt.Println("main process")
time.Sleep(time.Second)
}
goroutine 创建何时结束?
主程序结束/goroutine任务结束
使用waitGroup优雅解决time.sleep
func gowork(i int) {
defer wg.Done() // 任务结束
time.Sleep(time.Second * time.Duration(rand.Intn(10)))
fmt.Println("任务执行完毕", i)
}
// 模拟多个goroutine 执行任务结束后主程序结束
func main() {
for i := 0; i < 10; i++ {
wg.Add(1)
go gowork(i)
}
wg.Wait()
fmt.Println("主程序结束")
}
GMP模型
- G
- M
- P
work pool (goroutine 池/线程池)
channel
通过通信共享内存而不是通过共享内存而实现通信.goroutine 可以通过channel 进行数据交互,其数据遵循FIFO
声明定义
var b chan int
var a chan string
var c chan []int
func main() {
fmt.Println(b)
b = make(chan int) // 不带缓冲的初始化
b = make(chan int, 16) // 带缓冲的初始化
}
通道数据接收,发送,关闭
使用 “<-“ ,”->”
func channel(b *chan int) {
defer wg.Done()
transData := <-*b
fmt.Println("channel send data", transData)
close(*b)
}
func main() {
fmt.Println(b) // 未make 初始化为nil
wg.Add(1)
go channel(&b)
b = make(chan int) // 不带缓冲的初始化
b <- 10
}
有缓冲的channel
相比无缓冲的channel ,不需要提前指定数据消费处,也不会对程序造成deadlock!
func main() {
fmt.Println(b) // 未make 初始化为nil
wg.Add(1)
b = make(chan int, 16) // 带缓冲的初始化
b <- 10
go channel(&b) // 类似消费数据端.
wg.Wait()
}
单向channel
通道可以用来只发送,只接收,多用来对函数方法参数限制.
func coum(ch1 *<-chan int, ch2 *chan<- int) {
defer wg.Done()
for v := range *ch1 {
*ch2 <- v * v
}
close(*ch2)
}
其中ch1 被限制了在函数体内只能接收数据 ,ch2只能发送,否则错误
channel 错误情况
当channel 空时,接收数据会造成阻塞(deadlock),其他情况参照下表:
ps :关闭已经关闭的channel 会panic
select
如果多个操作都满足,则随机选取一个操作
select {
case 通道操作1:
case 通道操作2:
default :
当所有情况都不成立,走此处
}
Sync
互斥锁
使用互斥锁 能够使有且只有一个goroutine 能够读写共享资源
var wg sync.WaitGroup
var index = 0
var lock sync.Mutex
// 互斥锁
func add() {
defer wg.Done()
for i := 0; i < 50000; i++ {
lock.Lock()
index = index + 1
lock.Unlock()
}
}
func main() {
wg.Add(2)
go add()
go add()
wg.Wait()
fmt.Print(index)
}
读写互斥锁
实际应用,大部分的场景是读多写少,使用读写锁能一定程度优化程序性能,当然还有其他问题
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
var rwlock sync.RWMutex
var lock sync.Mutex
var (
x = 0
)
func write() {
wg.Done()
//lock.Lock()
rwlock.Lock()
x = x + 1
time.Sleep(time.Millisecond * 5)
// lock.Unlock()
rwlock.Unlock()
}
func read() {
defer wg.Done()
// lock.Lock()
rwlock.RLock()
fmt.Println(x)
time.Sleep(time.Millisecond)
// lock.Unlock()
rwlock.RUnlock()
}
func main() {
startTime := time.Now() // 记录程序开始时间
for i := 0; i < 10; i++ { // 写少
wg.Add(1)
go write()
}
for j := 0; j < 1000; j++ {
wg.Add(1)
go read() // 读多
}
wg.Wait()
fmt.Println(time.Now().Sub(startTime))
}
sync.Once
只做一次
var loadOnce sync.Once
load.DO(func(){}) 方法只能是无参的函数,但是用函数闭包去解决这个问题
Sync.Map
内置map 不是并发安全,而Sync.map 不需要make 初始化,并且解决并发安全。其提供 Store(设置值),Load(获取值),LoadOrStore(没有就设置),Delete(删除),Range(遍历)
var Smap = sync.Map{}
func main() {
wg := sync.WaitGroup{}
for i := 0; i < 30; i++ {
wg.Add(1)
go func(i int) {
key := strconv.Itoa(i)
Smap.Store(key, i) // 设置值
value, _ := Smap.Load(key) // 读取值
fmt.Printf("key =:%v value = :%v \n", key, value)
wg.Done()
}(i)
}
wg.Wait()
}
atomic(原子操作)
func LoadInt32(addr *int32) (val int32) // load => 获取
func LoadInt64(addr *int64) (val int64)
func LoadUint32(addr *uint32) (val uint32)
func LoadUint64(addr *uint64) (val uint64)
func LoadUintptr(addr *uintptr) (val uintptr)
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
func StoreInt32(addr *int32, val int32) // Stroe =》写入
func StoreInt64(addr *int64, val int64)
func StoreUint32(addr *uint32, val uint32)
func StoreUint64(addr *uint64, val uint64)
func StoreUintptr(addr *uintptr, val uintptr)
func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
func AddInt32(addr *int32, delta int32) (new int32) // 修改
func AddInt64(addr *int64, delta int64) (new int64)
func AddUint32(addr *uint32, delta uint32) (new uint32)
func AddUint64(addr *uint64, delta uint64) (new uint64)
func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)
func SwapInt32(addr *int32, new int32) (old int32) //交换
func SwapInt64(addr *int64, new int64) (old int64)
func SwapUint32(addr *uint32, new uint32) (old uint32)
func SwapUint64(addr *uint64, new uint64) (old uint64)
func SwapUintptr(addr *uintptr, new uintptr) (old uintptr)
func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool) //比较
func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)
func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)
func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool)
func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool)
func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)
原子操作,操作形式较少,大部分处理数字数据,善用比加锁性能要好