Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Goroutine和Channel的的使用和一些坑以及案例分析

Goroutine和Channel的的使用和一些坑以及案例分析

作者头像
阿伟
发布于 2019-08-13 02:43:54
发布于 2019-08-13 02:43:54
1.5K00
代码可运行
举报
文章被收录于专栏:GoLang那点事GoLang那点事
运行总次数:0
代码可运行
简单认识一下Go的并发模型

简单聊一下并发模型,下一篇会单独全篇聊聊多种并发模型,以及其演进过程。

  • 硬件发展越来越快,多核cpu正是盛行,为了提高cpu的利用率,编程语言开发者们也是各显神通,Java的多线程,nodejs的多进程,golang的协程等,我想大家在平时开发中都应该在各自公司的监控平台上看到cpu利用率低到5%左右,内存利用率经常80%左右。
  • 软件运行的最小单位是进程,当一个软件或者应用程序启动时我们知道操作系统为其创建了一个进程;代码运行的最小单位是线程,我们平时编程时写的代码片段在程序跑起来后一定是在一个线程中运行的,而这个线程是属于这个进程创建的。

我们经常接触到的并发模型是多线程并发模型,而Go语言中的并发模型是CSP并发模型,这里简单介绍一这两种并发模型

  1. 多线程并发模型

多线程并发模型是在一个应用程序中同时存在多个执行流,这多个执行流通过内存共享,信号量,锁等方式进行通信,CPU在多个线程间进行上下文切换,从而达到并发执行,提高CPU利用率,其本质是内核态线程和用户态线程是一对一的关系

  1. CSP并发模型

CSP并发模型的意思将程序的执行和通信划分开来(Process和Channel),Process代表了执行任务的一个单元,Channel用来在多个单元之间进行数据交互,共享;Process内部之间没有并发问题,所有由通信带来的并发问题都被压缩在Channel中,使得聚合在一起,得到了约束,同步,竞争聚焦在Channel上,Go就是基于这种并发模型的,Go在线程的基础上实现了这一套并发模型(MPG),线程之上虚拟出了协程的概念,一个协程代表一个Process,但在操作系统级别调度的基本单位依然是线程,只是Go自己实现了一个调度器,用来管理协程的调度,M(Machine)代表一个内核线程,P(Process)代表一个调度器,G(Goroutine)代表一个协程,其本质是内核线程和用户态线程成了多对多的关系

Goroutine和Channel的使用
  • 如下代码运行起来,Go的主协程就启动起来了
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

func main(){
    fmt.Println("主协程启动")
}
  • 如何通过代码启动一个新的协程呢,通过go关键字启动一个新的协程,主协程启动后,等待新的协程启动执行
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

func main(){
    var wg sync.WaitGroup
    wg.Add(1)
    go func(){
        defer wg.Done()
        fmt.Println("新的协程启动")
    }()
    fmt.Println("主协程启动")
    //等待新的协程运行完毕,程序才退出
    wg.Wait()
}
  • channel一些介绍
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//通道分为两类:
//无缓冲区的通道
c := make(chan int)
//有缓冲区的通道
c := make(chan int,10)

//通道的操作
//往通道写入数据
c <- 1
//从通道读取数据,
//temp是读取到的值
//ok是返回此通道是否已被关闭
temp,ok := <- c

//关闭通道
close(c)

//遍历通道
for v :=  range c{
}
  • 两个协程之间如何通信呢?,那就是通过channel通道来实现,channel创建时可以指定是否带有缓冲区,如果不带缓冲区,那么当一个协程往通道中写入一个数据的时候,另一个协程必须读取,否则第一个协程就只能出去阻塞状态(也就是生产一个,消费一个),带有缓冲区的channel就理解为一个队列或者仓库,可以一下子生产很多个先暂存起来,慢慢消费。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

func main(){
    var wg sync.WaitGroup
    wg.Add(1)
    //不带缓冲区的channel
    c := make(chan string)
    go func(){        
        defer func(){                
            wg.Done()              
        }()        
        for{                
            //从通道中取出数据                
            temp := <- c               
            if temp == "写入数据3" {                        
                break               
            }        
        }
    }()
    //主协程循环往通道写入值
    for i:=1;i<4;i++{        
        c <- "写入数据"+strconv.Itoa(i)
    }
    //等待新的协程运行完毕,程序才退出
    wg.Wait()
}
//最终程序执行结果
/**
写入数据1
写入数据2
写入数据3
*/
  • 我们再来看一个用Goroutine和Channel实现的生产者消费者例子
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**生产者消费者的例子*/
func ProductAndConsumer() {        
    wg := sync.WaitGroup{}        
    wg.Add(1)        
    //带有缓冲区的通道
    cint := make(chan int, 10)        
    go func() {                
        //product  ,循环往通道中写入一个元素              
        for i := 0; i < 100; i++ {                       
            cint <- i                        
        }        
        //关闭通道
        close(cint)        
     }()        
    go func() {                
        defer wg.Done()                
        //consumer   遍历通道消费元素并打印        
        for temp := range cint {                        
            fmt.Println(temp) 
            //len函数可以查看当前通道元素个数
            fmt.Println("当前通道元素个数",len(cint))
        }        
    }()        
    wg.Wait()
}
使用中的一些坑
  • 向一个已关闭的channel写入数据会报错,但从一个已关闭的channel读取数据不会报错
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

func main(){
    c := make(chan int,10)
    close(c)
    c <- 1
}
//结果如下
panic: send on closed channel
  • 主程序在读取一个没有生产者的channel时会被判断为死锁,如果是在新开的协程中是没有问题的,同理主程序在往没有消费者的协程中写入数据时也会发生死锁
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

func main(){
    c := make(chan int,10)
    //从一个永远都不可能有值的通道中读取数据,会发生死锁,因为会阻塞主程序的执行
    <- c
}
func main(){
    c := make(chan int,10)
    //主程序往一个没有消费者的通道中写入数据时会发生死锁, 因为会阻塞主程序的执行
    c <- 1
}
//结果如下
fatal error: all goroutines are asleep - deadlock!
  • 当通道被两个协程操作时,如果一方因为阻塞导致另一放阻塞则会发生死锁,如下代码创建两个通道,开启两个协程(主协程和子协程),主协程从c2读取数据,子协程往c1,c2写入数据,因为c1,c2都是无缓冲通道,所以往c1写时会阻塞,从c2读取时也会会阻塞,从而发生死锁
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

func main(){
    c1 := make(chan int)
    c2 := make(chan int)
    go func(){
        c1 <- 1
        c2 <- 2
    }()
    <- c2
}
//结果
fatal error: all goroutines are asleep - deadlock!

通道死锁的一些注意事项,其实上面的死锁情况主要分为如下两种

  1. 不要往一个已经关闭的channel写入数据
  2. 不要通过channel阻塞主协程
一些经典案例看看Gorouting和Chanel的魅力
  • 先说说Go中select的概念,一个select语句用来选择哪个case中的发送或接收操作可以被立即执行。它类似于switch语句,但是它的case涉及到channel有关的I/O操作,或者换一种说法,select就是用来监听和channel有关的IO操作,当 IO 操作发生时,触发相应的动作,基本用法如下:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//select基本用法
select {
    case <- c1:
    // 如果c1成功读到数据,则进行该case处理语句
    case c2 <- 1:
    // 如果成功向c2写入数据,则进行该case处理语句
    default:
    // 如果上面都没有成功,则进入default处理流程
}
  • 案例一,多个不依赖的服务可以并发执行
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

func queryUserById(id int)chan string{
    c := make(chan string)
    go func(){
        c <- "姓名"+strconv.Itoa(id)
    }()
    return c
}

func main(){
    //三个协程同时并发查询,缩小执行时间,
    //本来一次查询需要1秒,顺序执行就得3秒,
    //现在并发执行总共1秒就执行完成
    name1 := queryUserById(1)
    name2 := queryUserById(2)
    name3 := queryUserById(3)
    //从通道中获取执行结果
    <- name1
    <- name2
    <- name3
}
  • 案例二:select 监听通道合并多个通道的值到一个通道
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

func queryUserById(id int)chan string{
    c := make(chan string)
    go func(){
        c <- "姓名"+strconv.Itoa(id)
    }()
    return c
}

func main(){    
    c1, c2, c3 := queryUserById(1), queryUserById(2), queryUserById(3)
    c := make(chan string)
    // 开一个goroutine监视各个信道数据输出并收集数据到信道c
    go func() { 
        for {
            // 监视c1, c2, c3的流出,并全部流入信道c
            select {
               case       
                   v1 := <- c1:        
                   c <- v1
               case       
                   v2 := <- c2:        
                   c <- v2
               case       
                   v3 := <- c3:       
                   c <- v3
            }
        }
    }()
    // 阻塞主线,取出信道c的数据
    for i := 0; i < 3; i++ {
         // 从打印来看我们的数据输出并不是严格的顺序
        fmt.Println(<-c) 
    }
}
  • 案例三:结束标志
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func main() {

    c, quit := make(chan int), make(chan int)
    go func() {
        c <- 2  // 添加数据
        quit <- 1 // 发送完成信号
    } ()
    for is_quit := false; !is_quit; {
        // 监视信道c的数据流出
        select { 
            case v := <-c: fmt.Printf("received %d from c", v)
            case <-quit: is_quit = true 
            // quit信道有输出,关闭for循环
        }
    }}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-08-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 GoLang那点事 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Golang学习笔记之并发.协程(Goroutine)、信道(Channel)
简单的理解一下,并发就是你在跑步的时候鞋带开了,你停下来系鞋带。而并行则是,你一边听歌一边跑步。 并行并不代表比并发快,举一个例子,当文件下载完成时,应该使用弹出窗口来通知用户。而这种通信发生在负责下载的组件和负责渲染用户界面的组件之间。在并发系统中,这种通信的开销很低。而如果这两个组件并行地运行在 CPU 的不同核上,这种通信的开销却很大。因此并行程序并不一定会执行得更快。 Go 原生支持并发。在Go中,使用 Go 协程(Goroutine)和信道(channel)来处理并发。
李海彬
2018/12/27
1.4K0
Golang学习笔记之并发.协程(Goroutine)、信道(Channel)
这些 channel 用法你都用起来了吗?
channel 通道是可以让一个 goroutine 协程发送特定值到另一个 goroutine 协程的通信机制。
阿兵云原生
2023/10/24
2730
这些 channel 用法你都用起来了吗?
盘点Golang并发那些事儿之二
上一节提到,golang中直接使用关键字go创建goroutine,无法满足我们的需求。主要问题如下
PayneWu
2021/06/10
4930
盘点Golang并发那些事儿之二
goroutine调度机制
主要基于三个基本对象上,G,M,P(定义在源码的src/runtime/runtime.h文件中)
golangLeetcode
2022/08/02
1.3K0
goroutine调度机制
什么时候用Goroutine?什么时候用Channel?
什么场景下用channel合适呢? 通过全局变量加锁同步来实现通讯,并不利于多个协程对全局变量的读写操作。 加锁虽然可以解决goroutine对全局变量的抢占资源问题,但是影响性能,违背了原则。 总结:为了解决上述的问题,我们可以引入channel,使用channel进行协程goroutine间的通信。 Go语言中的操作系统线程和goroutine的关系: 一个操作系统线程对应用户态多个goroutine。 go程序可以同时使用多个操作系统线程。 goroutine和OS线程是多对多的关系,即m:n。 Go
王中阳Go
2022/10/26
9750
Goroutine泄露的危害、成因、检测与防治
Go内存泄露,相当多数都是goroutine泄露导致的。 虽然每个goroutine仅占用少量(栈)内存,但当大量goroutine被创建却不会释放时(即发生了goroutine泄露),也会消耗大量内存,造成内存泄露。
fliter
2023/06/18
1.1K0
Goroutine泄露的危害、成因、检测与防治
GO 语言的并发模式你了解多少?
实际上,出现上述的情况,还是因为我们对于 GO 语言的并发模型和涉及的 GO 语言基础不够扎实,误解了语言的用法。
阿兵云原生
2023/10/24
3430
GO 语言的并发模式你了解多少?
Go语言笔记----goroutine和channel
如果M1对应处理器正在处理的G1阻塞住了,那么你猜猜P的本地队列里面的G2是等待直到阻塞结束呢?还是有什么好的办法可以让他不受阻塞影响,可以接着处理呢?
大忽悠爱学习
2022/05/10
2850
Go语言笔记----goroutine和channel
Go Channel(收藏以备面试)
Go语言采用CSP模型,让两个独立执行的程序通过消息传递的方式共享内存,Channel就是Golang用来完成消息通讯的数据类型。
一行舟
2022/08/25
4830
Go基础——channel通道
要想理解 channel 要先知道 CSP 模型。CSP 是 Communicating Sequential Process 的简称,中文可以叫做通信顺序进程,是一种并发编程模型,由 Tony Hoare 于 1977 年提出。简单来说,CSP 模型由并发执行的实体(线程或者进程)所组成,实体之间通过发送消息进行通信,这里发送消息时使用的就是通道,或者叫 channel。CSP 模型的关键是关注 channel,而不关注发送消息的实体。Go 语言实现了 CSP 部分理论,goroutine 对应 CSP 中并发执行的实体,channel 也就对应着 CSP 中的 channel。
羊羽shine
2019/05/28
7320
Go语言并发机制
Go语言的并发通过goroutine(直译应该是Go程)实现。goroutine是用户态的轻量级线程,因此上下文切换要比线程的上下文切换开销要小很多。
Steve Wang
2021/01/20
5730
Go并发编程
百度Go语言优势,肯定有一条是说Go天生就有支持并发的优势,其他语言支持多线程并发,需要一定的门槛,基础的积累,学习多线程、进程语法。在Go中,就不需要考虑这些,原生提供goroutine(协程),自动帮你处理任务,
用户9022575
2021/10/01
5620
go进阶(2) -深入理解Channel实现原理
Go的并发模型已经在https://guisu.blog.csdn.net/article/details/129107148 详细说明。
黄规速
2023/02/27
3290
go进阶(2) -深入理解Channel实现原理
Go两周入门系列-协程(goroutine)
协程是Go语言的关键特性,主要用于并发编程,协程是一种轻量级的线程,因为协程开销比较小,所以创建上万的协程也不是什么难事,下面介绍协程的基本用法。
用户10002156
2023/10/05
2760
Go两周入门系列-协程(goroutine)
channel
单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。
Michel_Rolle
2023/11/30
2.5K0
15.Go语言-通道
通道(channel) ,就是一个管道,可以想像成 Go 协程之间通信的管道。它是一种队列式的数据结构,遵循先入先出的规则。
面向加薪学习
2022/09/04
5810
Go channel 实现原理分析
channel一个类型管道,通过它可以在goroutine之间发送和接收消息。它是Golang在语言层面提供的goroutine间的通信方式。
孤烟
2020/09/27
7250
Go通关09:并发掌握,goroutine和channel声明与使用!
您诸位好啊,我是无尘,今天开始我们进入Go语言并发阶段,说到并发,先简单介绍下几个概念:进程、线程、携程,并发、并行。
微客鸟窝
2021/08/18
3840
Go通关09:并发掌握,goroutine和channel声明与使用!
Golang 基础:原生并发 goroutine channel 和 select 常见使用场景
goroutine 是由 Go 运行时(runtime)负责调度的、轻量的用户级线程。
张拭心 shixinzhang
2022/05/10
1.1K0
Golang 基础:原生并发 goroutine channel 和 select 常见使用场景
【实践】Golang的goroutine和通道的8种姿势
如果说php是最好的语言,那么golang就是最并发的语言。 支持golang的并发很重要的一个是goroutine的实现,那么本文将重点围绕goroutine来做一下相关的笔记,以便日后快速留恋。
辉哥
2019/08/18
1.5K0
相关推荐
Golang学习笔记之并发.协程(Goroutine)、信道(Channel)
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验