Go入门: 入门Go的Goroutine


Processes and Threads(线程和进程)

首先,先了解下,什么是线程? 什么是进程?



比较抽象的概念为: ”进程是资源分配的最小单位,线程是CPU调度的最小单位“
这样的解释似乎不太容易让人理解.
可以做一个简单的比喻:


进程==火车,线程==车厢

  • 线程在进程下行进(单纯的车厢无法运行)

  • 一个进程可以包含多个线程(一辆火车可以有多个车厢)

  • 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)

  • 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)

  • 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)

  • 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)

  • 进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)

  • 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁"

  • 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”

这样理解,可以更加清晰.
参考:https://www.zhihu.com/question/25532384

了解完成进程和线程的区别后,我们继续并发和并行的概念?


并行发并行理解



看了很多文章和视频,似乎我之前对并发和并行的概念也很模糊,甚至越绕越晕;
直到看到了Erlang之父Joe Armstrong 画的一张图.

image.png



并发: 两个队列交替使用一台咖啡机
并行: 两个队列同时使用两台咖啡机
这样就好理解了~

另外还有一些疑问:
并发是一个线程? 并行是多个线程?
答: 并发和并行都可以是多个线程,就看这些线程能不能同时被多个cpu执行,如果可以就说明是并行,而并发是多个线程被一个cpu轮流去执行。

并发程序并不要求cpu具备多核计算能力。在同一时间段内,多个线程会被分配一定的执行时间片,在cpu上被快速轮换执行。

而并行程序要求CPU提供多核并行计算的能力。同一时刻内,就有多个线程CPU上的多个核上同时执行指令。


了解了上面的知识后,我们继续了解本章节的goroutine.

goroutine

什么是goroutine?

Go 语言层面支持的 go 关键字,可以快速的让一个函数创建为 goroutine,我们可以认为 main 函数就是作为 goroutine 执行的。操作系统调度线程在可用处理器上运行,Go运行时调度 goroutines 在绑定到单个操作系统线程的逻辑处理器中运行(P)。即使使用这个单一的逻辑处理器和操作系统线程,也可以调度数十万 goroutine 以惊人的效率和性能并发运行。

Go语言的最大特色就是从语言层面支持并发,

参考: https://www.zhihu.com/question/33515481

简单了解goroutine的用法.

package main

import (
    "fmt"
    "time"
)

func say(s string) {
    for i:=0; i <3; i ++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}


func main() {
    go  say("hello world")
    time.Sleep(1000 * time.Millisecond)
    fmt.Println("over")
}



在上述代码中,为什么我们需要在main函数中去sleep等待呢?
因为如果不进行等待阻塞,main函数会由上至下顺序执行,可能无法执行say()方法,主进程就已经退出了!

那如果希望等待当前的 goroutine 执行完成,然后再接着往下执行,该怎么办?本文尝试介绍这类问题的解决方法。

package main

import (
    "time"
    "fmt"
)

func say(s string) {
    for i := 0; i < 3; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go say("hello world")
    fmt.Println("over!")
}

上述代码会输出 over!
因为goroutine以非阻塞的方式执行,他们会随着程序(主线程)的结束而消亡,这肯定不是我们需要的结果.

2改进版本

func main() {
    go say("hello world")
    time.Sleep(1000 * time.Millisecond)
    fmt.Println("over!")
}

这个是属于比较low的做法,最简单,直接,通过Sleep函数,等待goroutine执行完成。

3 使用channel

func main() {
    done := make(chan  bool) // 定义一个阻塞的chan
    go func() {
        for i:=0; i<3; i ++ {
            time.Sleep(100 *time.Millisecond)
            fmt.Println("hello world")
        }
        fmt.Println("发送done信号")
        done <- true // 当go func 执行完成后, 向channel发送一个信号
    }()

    fmt.Println("开始接受done信号")
    <- done // 收到信号后,才会继续往下走,如果没有收到done的信号,则一直阻塞
    fmt.Println("接受完成done信号")
    fmt.Println("over")
}

4 标准答案
Go 官方在sync包中提供了 WaitGroup类型来解决这个问题.

A WaitGroup waits for a collection of goroutines to finish. The main goroutine calls Add to set the number of goroutines to wait for. Then each of the goroutines runs and calls Done when finished. At the same time, Wait can be used to block until all goroutines have finished.



等待组等待goroutine集合完成。主goroutine调用Add可设置要等待的goroutine数。然后每个goroutine都会运行,并在完成后完成调用。同时,可以使用Wait阻塞,直到所有goroutine都完成

对于waitGroup的使用可以总结为以下几点:

  • 创建一个waitgroup实例,比如名称为wg
  • 调用 wg.Add(n),其中:n 是等待的goroutine的数量
  • 在每个goroutine运行的函数中执行 defer wg.Done()
  • 调用 wg.Wait() 阻塞主逻辑
package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    say2("hello", &wg)
    say2("world", &wg)
    fmt.Println("over!")
}

func say2(s string, waitGroup *sync.WaitGroup) {
    defer waitGroup.Done()

    for i := 0; i < 3; i++ {
        fmt.Println(s)
    }
}


5 空的select 将永远阻塞
Keep yourself busy or do the work yourself

func main(){
    http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
        fmt.Println("hello world")
    })

    go func() {
        if err := http.ListenAndServe(":8080",nil); err != nil {
            log.Fatal(err)
        }
    }()

    select {

    }
}


6 规范

  1. 在绝大多数情况下,建议我们把go的并发逻辑交给调用者
    2 .我们在使用并发时,一定要有能力去控制gouroutine的生命周期

参考地址:
https://www.cnblogs.com/sparkdev/p/10917536.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,050评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,538评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,673评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,622评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,047评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,974评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,129评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,893评论 0 209
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,654评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,828评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,297评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,619评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,326评论 3 243
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,176评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,975评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,118评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,909评论 2 278

推荐阅读更多精彩内容