并发与并行

  1. 并发

同一时间段内执行多任务,你和两人聊天,具体到某一个人的时候需要切换聊天窗口

  1. 并行

同一时刻执行多个任务,你和你朋友都在和女朋友聊天

goroutine

goroutine 类似线程.,由runtime调度和管理

  1. 在linux 内核中,实际上线程也是”进程”,goroutine 是用户态线程
  2. 其分配栈空间大小在开始时一般只有2kb,其大小会随着程序运行自动扩容或者缩小,而os

使用-go

  1. func main() {
  2. for i := 0; i < 100; i++ {
  3. go func(i int) {
  4. fmt.Println(i)
  5. }(i)
  6. }
  7. fmt.Println("main process")
  8. time.Sleep(time.Second)
  9. }

goroutine 创建何时结束?

主程序结束/goroutine任务结束
使用waitGroup优雅解决time.sleep

  1. func gowork(i int) {
  2. defer wg.Done() // 任务结束
  3. time.Sleep(time.Second * time.Duration(rand.Intn(10)))
  4. fmt.Println("任务执行完毕", i)
  5. }
  6. // 模拟多个goroutine 执行任务结束后主程序结束
  7. func main() {
  8. for i := 0; i < 10; i++ {
  9. wg.Add(1)
  10. go gowork(i)
  11. }
  12. wg.Wait()
  13. fmt.Println("主程序结束")
  14. }

GMP模型

  • G
  • M
  • P

work pool (goroutine 池/线程池)

channel

通过通信共享内存而不是通过共享内存而实现通信.goroutine 可以通过channel 进行数据交互,其数据遵循FIFO

声明定义

  1. var b chan int
  2. var a chan string
  3. var c chan []int
  4. func main() {
  5. fmt.Println(b)
  6. b = make(chan int) // 不带缓冲的初始化
  7. b = make(chan int, 16) // 带缓冲的初始化
  8. }

通道数据接收,发送,关闭

使用 “<-“ ,”->”

  1. func channel(b *chan int) {
  2. defer wg.Done()
  3. transData := <-*b
  4. fmt.Println("channel send data", transData)
  5. close(*b)
  6. }
  7. func main() {
  8. fmt.Println(b) // 未make 初始化为nil
  9. wg.Add(1)
  10. go channel(&b)
  11. b = make(chan int) // 不带缓冲的初始化
  12. b <- 10
  13. }

有缓冲的channel

相比无缓冲的channel ,不需要提前指定数据消费处,也不会对程序造成deadlock!

  1. func main() {
  2. fmt.Println(b) // 未make 初始化为nil
  3. wg.Add(1)
  4. b = make(chan int, 16) // 带缓冲的初始化
  5. b <- 10
  6. go channel(&b) // 类似消费数据端.
  7. wg.Wait()
  8. }

单向channel

通道可以用来只发送,只接收,多用来对函数方法参数限制.

  1. func coum(ch1 *<-chan int, ch2 *chan<- int) {
  2. defer wg.Done()
  3. for v := range *ch1 {
  4. *ch2 <- v * v
  5. }
  6. close(*ch2)
  7. }

其中ch1 被限制了在函数体内只能接收数据 ,ch2只能发送,否则错误

channel 错误情况

当channel 空时,接收数据会造成阻塞(deadlock),其他情况参照下表:
image.png

ps :关闭已经关闭的channel 会panic

select

如果多个操作都满足,则随机选取一个操作

  1. select {
  2. case 通道操作1:
  3. case 通道操作2:
  4. default :
  5. 当所有情况都不成立,走此处
  6. }

Sync

互斥锁

使用互斥锁 能够使有且只有一个goroutine 能够读写共享资源

  1. var wg sync.WaitGroup
  2. var index = 0
  3. var lock sync.Mutex
  4. // 互斥锁
  5. func add() {
  6. defer wg.Done()
  7. for i := 0; i < 50000; i++ {
  8. lock.Lock()
  9. index = index + 1
  10. lock.Unlock()
  11. }
  12. }
  13. func main() {
  14. wg.Add(2)
  15. go add()
  16. go add()
  17. wg.Wait()
  18. fmt.Print(index)
  19. }

读写互斥锁

实际应用,大部分的场景是读多写少,使用读写锁能一定程度优化程序性能,当然还有其他问题

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. "time"
  6. )
  7. var wg sync.WaitGroup
  8. var rwlock sync.RWMutex
  9. var lock sync.Mutex
  10. var (
  11. x = 0
  12. )
  13. func write() {
  14. wg.Done()
  15. //lock.Lock()
  16. rwlock.Lock()
  17. x = x + 1
  18. time.Sleep(time.Millisecond * 5)
  19. // lock.Unlock()
  20. rwlock.Unlock()
  21. }
  22. func read() {
  23. defer wg.Done()
  24. // lock.Lock()
  25. rwlock.RLock()
  26. fmt.Println(x)
  27. time.Sleep(time.Millisecond)
  28. // lock.Unlock()
  29. rwlock.RUnlock()
  30. }
  31. func main() {
  32. startTime := time.Now() // 记录程序开始时间
  33. for i := 0; i < 10; i++ { // 写少
  34. wg.Add(1)
  35. go write()
  36. }
  37. for j := 0; j < 1000; j++ {
  38. wg.Add(1)
  39. go read() // 读多
  40. }
  41. wg.Wait()
  42. fmt.Println(time.Now().Sub(startTime))
  43. }

sync.Once

只做一次

  1. var loadOnce sync.Once
  2. load.DO(func(){}) 方法只能是无参的函数,但是用函数闭包去解决这个问题

Sync.Map

内置map 不是并发安全,而Sync.map 不需要make 初始化,并且解决并发安全。其提供 Store(设置值),Load(获取值),LoadOrStore(没有就设置),Delete(删除),Range(遍历)

  1. var Smap = sync.Map{}
  2. func main() {
  3. wg := sync.WaitGroup{}
  4. for i := 0; i < 30; i++ {
  5. wg.Add(1)
  6. go func(i int) {
  7. key := strconv.Itoa(i)
  8. Smap.Store(key, i) // 设置值
  9. value, _ := Smap.Load(key) // 读取值
  10. fmt.Printf("key =:%v value = :%v \n", key, value)
  11. wg.Done()
  12. }(i)
  13. }
  14. wg.Wait()
  15. }

atomic(原子操作)

  1. func LoadInt32(addr *int32) (val int32) // load => 获取
  2. func LoadInt64(addr *int64) (val int64)
  3. func LoadUint32(addr *uint32) (val uint32)
  4. func LoadUint64(addr *uint64) (val uint64)
  5. func LoadUintptr(addr *uintptr) (val uintptr)
  6. func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
  7. func StoreInt32(addr *int32, val int32) // Stroe =》写入
  8. func StoreInt64(addr *int64, val int64)
  9. func StoreUint32(addr *uint32, val uint32)
  10. func StoreUint64(addr *uint64, val uint64)
  11. func StoreUintptr(addr *uintptr, val uintptr)
  12. func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
  13. func AddInt32(addr *int32, delta int32) (new int32) // 修改
  14. func AddInt64(addr *int64, delta int64) (new int64)
  15. func AddUint32(addr *uint32, delta uint32) (new uint32)
  16. func AddUint64(addr *uint64, delta uint64) (new uint64)
  17. func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)
  18. func SwapInt32(addr *int32, new int32) (old int32) //交换
  19. func SwapInt64(addr *int64, new int64) (old int64)
  20. func SwapUint32(addr *uint32, new uint32) (old uint32)
  21. func SwapUint64(addr *uint64, new uint64) (old uint64)
  22. func SwapUintptr(addr *uintptr, new uintptr) (old uintptr)
  23. func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)
  24. func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool) //比较
  25. func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)
  26. func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)
  27. func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool)
  28. func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool)
  29. func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)

原子操作,操作形式较少,大部分处理数字数据,善用比加锁性能要好