Go并发编程1-并发模型

一. 并发概念

1. 多核CPU

  • 单核CPU主频接近4GHz时遇到瓶颈(能耗和散热),所以2005年4月Intel推出第一次双核奔腾CPU。
  • 多核CPU就是在一枚处理器中集成两个或以上计算引擎(内核),以线程方式执行多任务。
  • 单芯片多处理器即CMP,他分为同构异构两类,异构如CPUGPU的组合。
  • CMP通过总线共享的Cache结构片上的互连结构进行数据共享与同步,如下图:
    在这里插入图片描述

查看CPU

# 查看CPU信息(Linux)
$ cat /proc/cpuinfo

# 查看CPU信息(Mac)
$ sysctl machdep.cpu|grep count

CPU参考实例:

  型号名称:	MacBook Pro
  型号标识符:	MacBookPro14,1
  处理器名称:	Intel Core i5
  处理器速度:	2.3 GHz
  处理器数目:	1
  核总数:	2
  L2 缓存(每个核):	256 KB
  L3 缓存:	4 MB
  内存:    8 GB
  Boot ROM 版本:	MBP141.0173.B00
  SMC 版本(系统):	2.43f6
  序列号(系统):    FVFXR5KXXXXX
  硬件 UUID:	D1589F05-DF5C-5BEF-9943-XXXXXXXXXX

2. 进程与线程

  • 进程:资源分配的最小单位,进程由控制块(PCB)、程序和数据组成,生命周期:执行-就绪-阻塞。
  • 线程:cpu调度的最小单位 ,由进程创建,继承了进程的部分资源,具有进程的一些基本特征。

3. 并行与并发

  • 并行(parallelism):多个处理器上同时执行,同时刻
  • 并发(concurrency):同一处理器上轮转执行1,同时段

4. 串行与并发

  • 串行执行: 顺序执行,资源独占, 速度无关,结果确定
  • 并发执行: 无封闭性,不可再现, 相互制约,结果不定

5. 多核的利弊

  • 利:带来了CPU性能的提升,增强了用户体验
  • 弊:核数过多,对并发程序的编写、调试等造成困难,造成可编程性困扰

二. Go调度器

1. 基础知识:

  • Golang并发是基于函数的,即让某个函数独立运行的能力。
  • Golang并发基于非抢占式多任务处理,由协程主动交出控制权。
  • runtime.NumCPU()是通过超线程技术获取的本机逻辑CPU数量
  • Go1.5+默认的GOMAXPROCS为runtime.NumCPU(),适合cpu计算密集型

2. runtime调度器:

成员说明
GOARCHCPU架构,如x386、amd64、arm等
GOOS操作系统架构,如darwin, linux等
GOROOT()goroot目录
NumGoroutine()协程数(包含主协程)
NumCPU()逻辑cpu数
GOMAXPROCS(n)设置获取硬件线程数
GC()垃圾回收
ReadMemStats(&info)统计内存
Gosched()暂停当前协程
Goexit()终止当前协程

  • 示例1: 测试GOMAXPROCS
func sum(id int) {
   var x int64
   for i := 0; i < math.MaxUint32; i++ {
      x += int64(i)
   }
   println(id, x)
}

func main() {
   wg := new(sync.WaitGroup)
   wg.Add(2)
   for i := 0; i < 2; i++ {
      go func(id int) {
         defer wg.Done()
         sum(id)
      }(i)
   }
   wg.Wait()
}
GOMAXPROCS=2  time -p go run main.go 
0 9223372030412324865
1 9223372030412324865
real         1.72 // 程序开始到结束时间差 (非 CPU 时间) 
user         3.20 // ⽤用户态所使⽤用 CPU 时间片 (多核累加)
sys          0.09 // 内核态所使⽤用 CPU 时间片

调整GOMAXPROCS参数,观察real结果。


  • 示例2: 直观感受调度器调度过程
// 模拟时间片
func main() {
   //修改参数值,观察打印结果的离散度
   runtime.GOMAXPROCS(4)  
   foo := func(n int) {
      for {
         fmt.Print(n)
      }
   }
   go foo(0)
   go foo(1)
   time.Sleep(time.Second * 5)
   fmt.Println("~~~Done~~~")
}

  • 示例3: 退出子协程:runtime.Goexit将立即终止当前goroutine执行,调度器确保所有已注册defer延迟调用被执行。
func main() {
   wg := new(sync.WaitGroup)
   wg.Add(1)
   go func() {
      defer wg.Done()
      defer println("A.defer")
      func() {
         defer println("B.defer")
         runtime.Goexit()
         println("B")
      }()
      println("A")
   }()
   wg.Wait()
}

  • 示例4: 暂停子协程:runtime.Gosched让出底层线程,将当前goroutine暂停,放回队列等待下次被调度执行。
func main() {
   wg := new(sync.WaitGroup)
   wg.Add(2)
   go func() {
      defer wg.Done()
      println("Hello, World!")
   }()

   func() {
      defer wg.Done()
      for i := 0; i < 6; i++ {
         println(i)
         if i == 3 {
            runtime.Gosched()
         }
      }
   }()
   wg.Wait()
}

三. MPG模型

Go语言以 CSP(communicating sequential processes)并发模型。不同于传统的多线程通过共享内存来通信,CSP是**“以通信的方式来共享内存”**,
“不要以共享内存的方式来通信,相反,要通过通信来共享内存”

Do not communicate by sharing memory; instead, share memory by communicating.

Go语言使用MPG模式来实现CSP,在传统的并发中起很多线程只会加大CPU和内存的开销,太多的线程会大量的消耗计算机硬件资源,造成并发量的瓶颈。

  • M指的是“Machine”,一个M直接关联了一个内核线程。
  • P指的是”processor”,代表了M所需的上下文环境,也是处理用户级代码逻辑的处理器。
  • G指的是”Goroutine“,其实本质上也是一种轻量级的线程(逻辑态线程)。

在这里插入图片描述在这里插入图片描述
M关联了一个内核线程,通过调度器P(上下文)的调度,可以连接1个或者多个G,相当于把一个内核线程切分成了了N个用户线程,M和P是一对一关系(但是实际调度中关系多
变),通过P调度N个G(P和G是一对多关系),实现内核线程和G的多对多关系(M:N),通过这个方式, 一个内核线程就可以起N个Goroutine,同样硬件配置的机器可用的用
户线程就成几何级增长,并发性大幅提高。


参考:
https://blog.csdn.net/erlib/article/details/50264271
https://studygolang.com/articles/10099
https://www.jianshu.com/p/8aa03db51043
https://www.jianshu.com/p/ff8e8be262ac
https://blog.haohtml.com/archives/18352


  1. 出让时间片或控制权轮转执行 ↩︎

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值