goroutine

万大宝_我兜里有糖 · · 736 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

我们从调度上声明线程与goroutine的区别

为什么标题为goroutine,而不是协程,线程,其他语言之类,因为对于多线程编程来说,golang在此处是天生的高级语言

  • cpu已经通过分配时间,自带调度器实现切换时间片帮我们解决了多程序(任务)执行问题,在此基础上推演出更小单位多线程: 多线程的执行依赖os(操作系统)的调度分配,操作系统促使硬件调度时钟,隔个一段时间发送一个信号到cpu中,cpu结束当前执行线程的函数(程序)并将执行信息从寄存器保存到内存中,再查看线程清单中接下来要继续执行的线程(执行过程:内存中取出来发到寄存器(上下文切换)),并执行。以这种方式不断进行线程切换执行/恢复/切换/销毁 (1.操作系统本身也会增加cpu负载,再加上硬件调度,属于三方调度,降低效率 2.cpu-寄存器速度大概是内存的100倍,如此切换执行,费时)
  • 上面是大众的多线程运行方式,我们来看一下goroutine的运行原理,go是一种类似于c系语言编程,众多的包,编译的速度,二进制的直接执行文件,使得go开发效率极高。go运行于goroutine中,cpu运行时,本身自带线程调度器(程序调度器),使用一个go关键字即可实现多线程运行,但是并不是对应一个物理线程,假设调度器中有两个参数 A:B ,代码中goroutine数量为 A,物理线程为 B,(B的数量可配置,最好根据cpu核数来定义,此处不深究),动态自动的分配,销毁,降低了开发成本(锁的概念见下文),在goroutine中通过异步/阻塞来挂起线程,降低了线程切换成本

内存上

  • 线程运行要涉及内存保存信息,每一个线程都分配一个MB级别的内存存储信息。注意,此处是固定,'很多'线程时MB单位存储对内存是一种浪费,这就意味着限制了编程中线程开辟数量,浪费了cpu/内存资源。goroutine是'动态'分配大小,重要的是它的单位是KB,你可以忽略线程的消耗,尽情的创建(此处不严谨的猜想:线程可挂起并通过通道传递信息,不难发现按需增大缩小正是动态分配的关键 <b>通讯</b>)大家可能明白协程为啥这么畅销码农市场了

我们来搞几个demo玩一下

之前参考了几篇go语言中文社区的文章,发现人家的讲解已经很清晰了,所以这里我计划多一些我自己的理解(偏理论)的东西

go协程使用,需要用到的几种通讯方式有 sync,channel ... (不下三种,水平有限,只会channel),go的官网文档介绍[sync:大部分都是适用于低水平程序线程,高水平的同步使用channel通信更好一些。],channel也是golang的最为核心的技术(论程序员的修养:channal是google的大佬底层封装的好)

阻塞/挂起线程类似于锁的概念或者说php/js中断点,只不过是可通过通讯方式来开启/关闭

有缓冲channel

声明方式

ch := make(chan int, 3)

运行方式'异步', 仅限不阻塞情况下异步执行,阻塞后还是同步等待执行,第二个参数为缓冲的大小

使用

  1. 通道的缓存无数据,但执行读通道。
  2. 通道的缓存已经占满,向通道写数据,但无协程读。
  • 在缓冲范围内,两个goroutine不阻塞,异步执行,通过channel传输数据,超过缓冲长度时会阻塞等待消费者消费数据,消费后阻塞释放
  • 无缓冲通道的读取方式:通过range 类迭代方式读取,但此处要注意的是range不会根据通道的长度来作为循环的次数,而是根据通道是否关闭来限定次数,如不关闭通道,无线循环取数据,会发生阻塞等待,发展成死锁。
    ch := make(chan int, 3) 
    ch <- 7
    ch <- 8
    ch <- 9
    close(ch)  // 关闭通道
    for item := range ch{
        fmt.Println(item)
    }
    // stystem out : 7 8 9  //遵循先进先出顺序
    
    // 或者通过 人工 break 跳出循环
    ch := make(chan int, 3)
    ch <- 7
    ch <- 8
    ch <- 9
    for item := range ch {
        fmt.Println(item)
        if len(ch) == 0 {
            break    // 人工跳出循环
        }
    }

无缓冲channel

声明方式

ch := make(chan int)

运行方式'同步'

使用情况

  1. 通道中无数据,但执行读通道。
  2. 通道中无数据,向通道写数据,但无协程读取。
  • 生产消息/消费消息(一下成为存/取)是原子性,每一个动作都会阻塞当前线程(挂起线程,并非销毁,一直等待对应的存/取操作发生来解锁),这也就意味着多数情况下使用无缓冲通道必须要开辟两个goroutine,一个存一个取,只要存了就必须要取出来,不然会无限阻塞形成死锁。所以无缓冲channel中len永远是0;

    [阻塞:1.存进去就必须要取出来 2.如果本来就没数据去也可以取,但是要一直阻塞等待到其它goroutine存入数据]

示例方法为省代码量,全用闭包代替(主要是一些死锁情况)

存入无缓冲通道,此处触发阻塞,下面goroutine根本没机会执行,导致无线阻塞等待数据消费,形成死锁
    xChan := make(chan int)
    xChan<-1
    go func() {
        // code ...
        <-xChan
    }()
    fmt.Println("main done..  no  deadlock")
    // fatal error: all goroutines are asleep - deadlock!
换个位置,再来一次,阻塞位置变化,直到子线程取出数据,阻塞释放(串行编程,没招)
    xChan := make(chan int)
    go func() {
        // code ...
        <-xChan
    }()
    xChan<-1
    fmt.Println("main done..  no  deadlock")
    // main done..  no  deadlock
代码竟然没报错,这是因为主线程没进行读/取操作,不存在阻塞,根本就等到子线程执行就执行完了(所有的线程的生命周期都是伴随着主线程的终止而终止,调度器和cpu就算再强大,开辟线程也是需要时间的)
    c := make(chan int)
    go func() {
        c <- 1
    }()
    fmt.Println("main done..  no  deadlock")
这个例子是说无缓冲channel要伴随着两个goroutine搭配,如果换成有缓冲通道,在一个goroutine就不会报错,因为是异步的,类似于queque,但也是失去了并发编程的意义
    ch := make(chan int)
    // 写入数据,堵塞当前线程, 没人取走数据阻塞不会释放
    ch <- 1 
    //在此行执行之前Go就会报死锁
    fmt.Println("main done..  no  deadlock")
多channel管理可用 select 语句进行管理,可通过设置default方式避免死锁发生
    // 每个 case 必须是一个通信操作,要么是发送要么是接收。
    // select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行(运行后终止)。一个默认的子句应该总是可运行的。
    input := make(chan interface{})
    // 另开一个线程添加chan数据
    go func() {
        for i := 0; i < 5; i++ {
            input <- i
        }
        input <- "hello, world"
    }()

    //os.Exit(22)
    // 主线程执行过快  没存进去 select 就开始取了
    time.Sleep(2*time.Second)

    select {
    // 3个case随机执行'一次',没有可执行的便执行defaule
        case msg1:=<-input:
            fmt.Println(msg1,"---1")
        case msg2:=<-input:
            fmt.Println(msg2,"---2")
        case msg3:=<-input:
            fmt.Println(msg3,"---3")
        default:
            fmt.Println("close")
    }
  • 无缓冲的信道是数据一个进一个出交替阻塞执行
  • 有缓冲信道则是一个一个存储,一个个取出,异步队列不阻塞执行
                这一期本来介绍一款虚拟机,但是想来想去打算学习一下docker后结合起来发表会更好,本人底层知识实属有限理解不到位,望各位兄台批评指正。
                本故事纯属虚构

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:万大宝_我兜里有糖

查看原文:goroutine

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

736 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传