golang中的CSP并发模型

1. 相关概念:
  用户态:当一个进程在执行用户自己的代码时处于用户运行态(用户态)
  内核态:当一个进程因为系统调用陷入内核代码中执行时处于内核运行态(内核态),引入内核态防止用户态的程序随意的操作内核地址空间,具有一定的安全保护作用。这种保护模式是通过内存页表操作等机制,保证进程间的地址空间不会相互冲突,一个进程的操作不会修改另一个进程地址空间中的数据。
  用户态与内核态之间的切换:当在系统中执行一个程序时,大部分时间都是运行在用户态下的,在其需要操作系统帮助完成一些用户态自己没有特权和能力完成的操作时就会切换到内核态。有以下三种方式:
  (1)系统调用(中断)
    用户态进程主动要求切换到内核态的一种方式。
  (2)异常
    cpu运行时如果发生一些没有预知的异常,会触发当前进程切换到处理此异常的内核相关进程中。
  (3)外围设备的中断
    用户态进程主动要求切换到内核态的一种方式。

  协程:又称微线程,纤程。英文名Coroutine。Coroutine是一种运行在用户态的用户线程,类似于 greenthread。协程与线程都相互独立,且有自己的上下文,不同之处在于,协程的切换由其自身控制,而线程的切换收到系统调度。

2. CSP (通信顺序进程)
  CSP模型用来描述两个独立的并发实体通过共享的通讯channel管道进行通信的并发模型。
  golang借用了CSP模型的一些概念如:实体 process,通道 channel,为之实现并发进行了理论支持,实际上并没有完全实现CSP模型的所有理论。process是在go语言上的表现就是goroutine,是实际上并发执行的实体,每个实体之间是通过channel通讯来实现数据共享。

3. channel:同步&传递消息

  channel是被单独创建并且可以在进程之间传递,它的通信模式类似于boss-worker模式,一个实体通过将消息发送到channel中,然后又监听这个channel的实体处理,两个实体之间是匿名的,实现原理上其实是一个阻塞的消息队列。、

  具体可以分为:有/无缓存channel,只读channel,只写channel,双向channel

  写操作:chan <- value

  读操作:<- chan

// Create channel
// Unbuffered channel
umbuffer_chan := make(chan int)
// Buffered channel
// Buffer Size = 3
buffer_chan := make(chan int,3)

// Read-Only channel
read_channel := make(<-chan int)
// Receive-Only channel
receive_channel := make(chan<- int)

  生产者-消费者Sample:

package main
import (
   "fmt"
   "time"
)

// 生产者
func Producer (queue chan<- int){
        for i:= 0; i < 10; i++ {
                queue <- i
        }
}
// 消费者
func Consumer( queue <-chan int){
        for i :=0; i < 10; i++{
                v := <- queue
                fmt.Println("receive:", v)
        }
}

func main(){
        queue := make(chan int, 1)
        go Producer(queue)
        go Consumer(queue)
        time.Sleep(1e9) //让Producer与Consumer完成

}

4. goroutine:实际并发执行的实体

  在函数或者方法前面加上关键字go,就创建了并发运行的goroutine,eg:

  go func (){
  }

  func Test(){
  }
  // ...
  go Test()

  实例代码:

package main  // 代码包声明语句。
import (
   "fmt" //系统包用来输出的
   "math/rand"
   "runtime"
   "sync"
   "time"
)

func main() {
   // 分配一个逻辑处理器给调度器使用
   runtime.GOMAXPROCS(1)

   // WaitGroup是一个计数信号量,用来记录和维护运行的goroutine,如果当前的wg>0,对应的exit方法就会阻塞
   var wg sync.WaitGroup
   // 计数加2表示要等待两个goroutine
   wg.Add(2)

   fmt.Printf("Start Goroutines \n", )

   // 声明匿名函数,创建goroutine
   go func(){
      // 关键字defer会修改函数调用时机,在函数退出时调用Done来通知main函数工作已经完成
      defer wg.Done()

      for count:=0; count<3; count++ {
         for char :=‘a‘; char<‘a‘+26 ; char++ {
            fmt.Printf("%c ", char)
         }
      }
   }()

   // 声明匿名函数,创建goroutine
   go func() {
      // 函数退出时调用Done来通知main函数工作已经完成
      defer wg.Done()

      for count:=0; count<3; count++ {
         for char :=‘A‘; char<‘A‘+26 ; char++ {
            fmt.Printf("%c ", char)
         }
      }
   }()

   fmt.Println("Waiting to finish!\n", )
   // 等待结束
   wg.Wait()

   fmt.Println("\nTerminate program! \n", )
}

5. golang调度器
  OS在物理处理器上调度线程来运行,而golang在逻辑处理器上调度goroutine来运行。每个逻辑处理器都分别绑定到单个操作系统线程。
  如果创建一个goroutine并准备运行,这个goroutine就会被放到调度器的全局运行队列中。之后,调度器就会将队列中的goroutine分配给一个逻辑处理器,并放到这个逻辑处理器对应的本地运行队列中。本地运行队列中的goroutine会一直等待,知道自己被分配到相应的逻辑处理器上运行。
  eg:

  其中:
  M:Machine,一个M直接关联了一个内核线程。
  P:Processor,代表了M所需要的上下文环境,也就是处理用户级代码逻辑的处理器。
  G:Goroutine,本质上是一种轻量级的线程--协程。
  MPG模型,三者关系的宏观图为:

  

  Processor的作用:
  当内核线程阻塞的时候,由于上下文的存在,我们能够直接放开其他线程,继续去执行未阻塞的线程,例子如下:

  

  如果当前,G0由于I/O,系统调用进行了阻塞,这个时候M0就可以放开其他的线程:

  

  M0和G0进行系统调用,等待返回值,上下文P以及routine队列交由M1进行执行。当M0执行系统调用结束后,M0会尝试去steal("偷")一个上下文,如果不成功,M0就把它的G0放到一个全局的运行队列中,然后将自己放到线程池或者转入休眠状态。
  Global runqueue是各个上下文P在运行完自己的本地的goroutine runqueue后用来拉取新的goroutine的地方(steal working算法)。此外,P也会周期性的检查Global runqueue上的goroutine,来防止全局上的goroutine因为得不到执行而饿死。

原文地址:https://www.cnblogs.com/ThomasYuan/p/10745407.html

时间: 2024-08-29 18:38:47

golang中的CSP并发模型的相关文章

Golang百万级高并发实例

前言 感谢Handling 1 Million Requests per Minute with Go这篇文章给予的巨大启发. 基础 我们使用Go语言,基本上是因为他原生支持的高并发:Goroutine 和 Channel:Go 的并发属于 CSP 并发模型的一种实现:CSP 并发模型的核心概念是:"不要通过共享内存来通信,而应该通过通信来共享内存". 简单用法 我一开始学习Go语言的时候,遇到大访问量的时候,会先创建一个带缓冲的channel,然后起一个Go协程来逐个读取channe

Golang并发模型:select进阶

前一篇文章<Golang并发模型:轻松入门select>介绍了select的作用和它的基本用法,这次介绍它的3个进阶特性. nil的通道永远阻塞 如何跳出for-select select{}阻塞 nil的通道永远阻塞 当case上读一个通道时,如果这个通道是nil,则该case永远阻塞.这个功能有1个妙用,select通常处理的是多个通道,当某个读通道关闭了,但不想select再继续关注此case,继续处理其他case,把该通道设置为nil即可.下面是一个合并程序等待两个输入通道都关闭后才退

golang常见的几种并发模型框架

原文链接 package main import ( "fmt" "math/rand" "os" "runtime" "sync" "sync/atomic" "time" ) type Scenario struct { Name string Description []string Examples []string RunExample func() } v

Golang 高效实践之并发实践

前言 在我前面一篇文章Golang受欢迎的原因中已经提到,Golang是在语言层面(runtime)就支持了并发模型.那么作为编程人员,我们在实践Golang的并发编程时,又有什么需要注意的点呢?下面我会跟大家详细的介绍一些在实际生产编程中很容易踩坑的知识点. CSP 在介绍Golang的并发实践前,有必要先介绍简单介绍一下CSP理论.CSP,全称是Communicating sequential processes,翻译为通信顺序进程,又翻译为交换消息的顺序程序,用来描述并发性系统的交互模式.

并发模型之——基本概念

从很久之前在学校到现在我们编程的时候经常都有听说到并发编程,偶尔也会听到说并行,但我们很多人其实都不太清除并发与并行具体的区别在哪:我们刚开始学习编程语言的时候我相信我们写的都是串行程序,一步接着一步来,可以说这比并发程序更不容易出错,但在性能上要远不如并发:还有一种并发具有很强容错性:分布式程序,分布式程序也算是并发程序,还可以具有很强的容错性,可以分开部署:     并发与并行有着本质上的区别.     并发指程序在同一时间只能做一个操作,但是可以在不同的时间点()做多个操作:     如:

golang中interface接口的深度解析

什么是interface,简单的说,interface是一组method的组合,下面这篇文章主要给大家深度解析了关于golang中的interface接口,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧. 一 接口介绍 如果说gorountine和channel是支撑起Go语言的并发模型的基石,让Go语言在如今集群化与多核化的时代成为一道亮丽的风景,那么接口是Go语言整个类型系列的基石,让Go语言在基础编程哲学的探索上达到前所

Golang 高效实践之并发实践context篇

前言 在上篇Golang高效实践之并发实践channel篇中我给大家介绍了Golang并发模型,详细的介绍了channel的用法,和用select管理channel.比如说我们可以用channel来控制几个goroutine的同步和退出时机,但是我们需要close channel通知其他接受者,当通知和通信的内容混在一起时往往比较复杂,需要把握好channel的读写时机,以及不能往已经关闭的channel中再写入数据.如果有没有一种更好的上下文控制机制呢?答案就是文章今天要介绍的context,

golang 碎片整理之 并发

并发与并行 并发:同一时间段内执行多个任务.并行:同一时刻执行多个任务. Go语言的并发通过goroutine实现.goroutine类似于线程,属于用户态的线程,我们可以根据需要创建成千上万个goroutine并发工作.goroutine是由go语言的运行调度完成的,而线程是由操作系统调度完成的.Go语言还提供了channel在多个goroutine间进行通信.goroutine和channel是go语言秉承的CSP(Communicating Sequential Process)并发模式的

并发模型与IO模型梳理

并发模型 常见的并发模型一般包括3类,基于线程与锁的内存共享模型,actor模型和CSP模型,其中尤以线程与锁的共享内存模型最为常见.由于go语言的兴起,CSP模型也越来越受关注.基于锁的共享内存模型与后两者的主要区别在于,到底是通过共享内存来通信,还是通过通信来实现访问共享内存.由于actor模型和CSP模型,本人并不是特别了解,我主要说说最基本的并发模型,基于线程与锁的内存共享模型. 为什么要并发,本质都是为了充分利用多核CPU资源,提高性能.但并发又不能乱,为了保证正确性,需要通过共享内存