Go基础教程05-并发

goroutine 协程

协程是goruntime管理的轻量级线程, go f(args)会启动一个goroutine并执行f(args), f和args的求值在当前的goroutine中, 而f的执行在新的goroutine中

package main

import (
    "fmt"
    "time"
)

func f(tag string) {
    for i := 0; i < 3; i++ {
        fmt.Println(tag, "-", i)
    }
}

func main() {
    f("main thread")
    go f("goroutine")
    go func() {
        fmt.Println("takeing ...")
    }()
    time.Sleep(time.Second)
    fmt.Println("finished")
}
//main thread - 0
//main thread - 1
//main thread - 2
//goroutine - 0
//takeing ...
//goroutine - 1
//goroutine - 2
//finished

可以看到2个协程是交替输出的, 表示go runtime是以并发的方式运行协程的, 等待协程的完成我们这里用的是sleep, 更好的方式是用WaitGroup.

信道

channels可以连接多个协程, 从一个协程将值发送到信道, 在另一个协程中接收, 使用make(chan val-type)创建一个新通道, 类型就是需要传递值的类型, 使用channel <- val把val发送到通道中, 使用<-channel从通道中接收值, 箭头方向是数据流的方向;默认的发送和接收操作是阻塞的, 直到发送和接收方都就绪,这可以使goroutine在没有显式的锁和竟态变量的情况下同步.

package main

import "fmt"

func main() {
    msgs := make(chan string)

    go func() {
        msgs <- "ping"
    }()

    msg := <-msgs
    fmt.Println(msg)
}

信道缓冲

默认情况下, 信道是无缓冲的, 也就是说对应的接收方准备好接收时,才允许发送, 有缓冲的信道允许没有接收者时, 缓存一定数量的值.

package main

import "fmt"

func main() {
    msgs := make(chan string, 2)
    msgs <- "ping"
    msgs <- "pong"
    fmt.Println(<-msgs) //ping
    fmt.Println(<-msgs) //pong
}

信道同步

利用信道阻塞的特性来通知协程的函数完成.

package main

import (
    "fmt"
    "time"
)

func ping(done chan bool) {
    fmt.Println("ping")
    time.Sleep(time.Second)
    fmt.Println("pong")
    done <- true
}

func main() {
    done := make(chan bool)
    go ping(done)
    <-done  // 如果没有接收, 程序不等协程执行就会直接退出了
}

信道方向

chan作为函数参数时, 可以指定信道是否为只读或者只写.

package main

import "fmt"

func ping(p chan<- string, msg string) {
    p <- msg
}

func pong(p <-chan string, q chan<- string) {
    msg := <-p
    q <- msg
}

func main() {
    p := make(chan string, 1)
    q := make(chan string, 1)
    ping(p, "~~~~~~")
    pong(p, q)
    fmt.Println(<-q)
}

range和close

发送者可以通过close关闭信道, 接收者可以用v,ok:=<-ch测试信道是否关闭, 如果关闭,ok为false.

只有发送者才能关闭信道, 向关闭的信道发送数据会引发panic

信道通常情况下不需要关闭, 只有在必须告诉接收者是否还需要接收时才有必要关闭,比如终止循环.

package main

import "fmt"

func fib(n int, c chan int) {
    a, b := 0, 1
    for i := 0; i < n; i++ {
        c <- a
        a, b = b, a+b
    }
    close(c)
}

func main() {
    c := make(chan int, 10)
    go fib(cap(c), c)
    for i := range c {
        fmt.Println(i)
    }
}

信道选择器

select选择器可以让你同时等待多个信道操作.

package main

import (
    "fmt"
    "time"
)

func main() {
    start := time.Now()
    c1 := make(chan string)
    c2 := make(chan string)

    go func() {
        time.Sleep(1 * time.Second)
        c1 <- "one"
    }()

    go func() {
        time.Sleep(2 * time.Second)
        c2 <- "two"
    }()
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-c1:
            fmt.Println("revc:", msg1)
        case msg2 := <-c2:
            fmt.Println("revc:", msg2)
        }
    }
    fmt.Println("use time:", time.Now().Sub(start))
}
//revc: one
//revc: two
//use time: 2.00109499s

如预期一样, 先收到one,后收到two

超时处理

<-time.After()等待超时几秒, 我们等待时间长一点就成功接收到c2的值了,select 会阻塞到某个分支可以继续执行为止,这时就会执行该分支;当多个分支都准备好时会随机选择一个执行.

package main

import (
    "fmt"
    "time"
)

func main() {
    c1, c2 := make(chan string, 1), make(chan string, 1)
    go func() {
        time.Sleep(2 * time.Second)
        c1 <- "1"
    }()
    select {
    case r1 := <-c1:
        fmt.Println(r1)
    case <-time.After(time.Second):
        fmt.Println("timeout 1")
    }

    go func() {
        time.Sleep(2 * time.Second)
        c2 <- "2"
    }()
    select {
    case r2 := <-c2:
        fmt.Println(r2)
    case <-time.After(3 * time.Second):
        fmt.Println("timeout 2")
    }
}
//timeout 1
//2

互斥锁

如果在并发中不需要通信, 只想保证每次只要一个协程能访问一个共享变量, 就需要用互斥锁来提供这种机制, sync.Mutex互斥锁提供了Lock和UnLock两个方法.

package main

import (
    "fmt"
    "sync"
    "time"
)

type Counter struct {
    c  map[string]int
    mu sync.Mutex
}

func (co *Counter) inc(name string) {
    co.mu.Lock()
    defer co.mu.Unlock() // 函数结束时解锁
    co.c[name]++
}

func main() {
    co := Counter{
        c: map[string]int{"test": 0},
    }
    for i := 0; i < 1000; i++ {
        go co.inc("test")
    }
    time.Sleep(time.Second)
    fmt.Println(co.c["test"])
}

学习更多Golang知识

不负春光 不负自己

戳“更多文章”我们一起进步

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

推荐阅读更多精彩内容

  • 前言:本专题用于记录自己(647)在Go语言方向的学习和积累。系列内容比较偏基础,推荐给想要入门Go语言开发者们阅...
    齐舞647阅读 869评论 0 4
  • goroutine 并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。并发(concurr...
    Spring618阅读 545评论 0 0
  • Go 通过协程实现并发,协程之间靠信道通信 1.1 并发、并行是什么? 并行其实很好理解,就是同时执行的意思,在某...
    将军红阅读 1,769评论 0 4
  • 上一篇(一日一学_Go从错误中学习基础一)讲了部分Golang容易出错地方,为了让读者清晰学习,我决定分开。 ne...
    WuXiao_阅读 931评论 4 4
  • 单纯地将函数并发执行是没有意义的,函数与函数之间需要交换数据才能体现并发执行函数的作用。虽然可使用共享内存进行数据...
    JunChow520阅读 424评论 0 2