Golang 并发编程指南

本文深入探讨了Golang的并发编程,包括Goroutine、调度器结构、channel的使用、并发控制以及同步机制。重点讲解了Goroutine的工作原理、channel的创建和操作,以及如何利用channel实现并发安全和并发数量控制。还提到了Mutex、Atomic、WaitGroup等并发基础库的使用,并给出了实际的开发案例和最佳实践。
摘要由CSDN通过智能技术生成
分享 Golang 并发基础库,扩展以及三方库的一些常见问题、使用介绍和技巧,以及对一些并发库的选择和优化探讨。

go 原生/扩展库

提倡的原则

不要通过共享内存进行通信;相反,通过通信来共享内存。

Goroutine

goroutine 并发模型

调度器主要结构

主要调度器结构是 M,P,G

  1. M,内核级别线程,goroutine 基于 M 之上,代表执行者,底层线程,物理线程
  2. P,处理器,用来执行 goroutine,因此维护了一个 goroutine 队列,里面存储了所有要执行的 goroutine,将等待执行的 G 与 M 对接,它的数目也代表了真正的并发度( 即有多少个 goroutine 可以同时进行 );
  3. G,goroutine 实现的核心结构,相当于轻量级线程,里面包含了 goroutine 需要的栈,程序计数器,以及所在 M 的信息

P 的数量由环境变量中的 GOMAXPROCS 决定,通常来说和核心数对应。

映射关系

用户空间线程和内核空间线程映射关系有如下三种:

  1. N:1
  2. 1:1
  3. M:N

调度图

关系如图,灰色的 G 则是暂时还未运行的,处于就绪态,等待被调度,这个队列被 P 维护

注: 简单调度图如上,有关于 P 再多个 M 中切换,公共 goroutine 队列,M 从线程缓存中创建等步骤没有体现,复杂过程可以参考文章简单了解 goroutine 如何实现

goroutine 使用

  • demo1
    go list.Sort()
     
  • demo2
    funcAnnounce(message string, delay time.Duration) {
    gofunc() {
    time.Sleep(delay)
    fmt.println(message)
    }()
    }
     

channel

channel 特性

创建

// 创建 channel
a := make(chan int)
b := make(chan int, 10)
// 单向 channel
c := make(chan<- int)
d := make(<-chan int)

存入/读取/关闭

tip:

v, ok := <-a  // 检查是否成功关闭(ok = false:已关闭)

channel 使用/基础

use channel
 ci := make(chan int)
cj := make(chan int, 0)
cs := make(chan *os.File, 100)

 c := make(chan int)
go func() {
    list.Sort()
    c <- 1
}()
doSomethingForValue
<- c

 func Server(queue chan *Request) {
 for req := range queue {
      sem <- 1
 go func() {
            process(req)
            <- sem
        }()
    }
}

 func Server(queue chan *Requet) {
 for req := range queue {
      sem <- 1
 go func(req *Request) {
          process(req)
            <- sem
        }(req)
    }
}

 func Serve(queue chan *Request) {
 for req := range queue {
        req := req
        sem <- 1
 go func() {
            process(req)
          <-sem
        }()
    }
}

channel 使用/技巧

等待一个事件,也可以通过 close 一个 channel 就足够了。。

c := make(chan bool)
go func() {
    // close 的 channel 会读到一个零值
    close(c)
}()
<-c

阻塞程序

开源项目【是一个支持集群的 im 及实时推送服务】里面的基准测试的案例

资料领取直通车:Golang云原生最新资料+视频学习路线icon-default.png?t=M85Bhttps://docs.qq.com/doc/DTllySENWZWljdWp4

Go语言学习地址:Golang DevOps项目实战icon-default.png?t=M85Bhttps://ke.qq.com/course/422970?flowToken=1043212

取最快结果

func main() {
 ret := make(chan string, 3)
 for i := 0; i < cap(ret); i++ {
  go call(ret)
 }
    fmt.Println(<-ret)
}
func call(ret chan<- string) {
 // do something
 // ...
 ret <- "result"
}

协同多个 goroutines

注: 协同多个 goroutines 方案很多,这里只展示 channel 的一种。

limits := make(chan struct{}, 2)
for i := 0; i < 10; i++ {
 go func() {
        // 缓冲区满了就会阻塞在这
  limits <- struct{}{}
  do()
  <-limits
 }()
}

搭配 select 操作

for {
 select {
    case a := <- testChanA:
        // todo a
    case b, ok := testChanB:
        // todo b, 通过 ok 判断 tesChanB 的关闭情况
    default:
        // 默认分支
    }
}

main go routinue 确认 worker goroutinue 真正退出的方式

func worker(testChan chan bool) {
    for {
     select {
        // todo some
  // case ...
        case <- testChan:
         testChan <- true
         return
     }
 }
}

func main() {
    testChan := make(chan bool)
    go worker(testChan)
    testChan <- true
    <- testChan
}

关闭的 channel 不会被阻塞

testChan := make(chan bool)
close(testChan)

zeroValue := <- testChan
fmt.Println(zeroValue) // false

testChan <- true // panic: send on closed channel

注: 如果是 buffered channel, 即使被 close, 也可以读到之前存入的值,读取完毕后开始读零值,写入则

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值