Golang 协程和管道

1.协程(goroutiue)

在go语言中,并发的实现是通过协程来实现的。语法上使用go关键字加函数即可让函数以并发的方式执行。
注意, go语言是以通信的方式来共享内存的,而不是通过共享内存来通信的。
协程的异常保护,子协程的异常如果自己不处理,会向上抛出给父协程。直至影响主协程挂掉,程序停止。举个简单的例子:

package main

import (
    "fmt"
)

func main() {
    c := make(chan bool)
    go func () {
        fmt.Println("go func....")
        c <- true
        close(c)
        c <- true
    }()
    for v:= range c {
        fmt.Println(v)
    }
    fmt.Println("main done...")
}
//==================
go func....
panic: send on closed channel

goroutine 17 [running]:
main.main.func1(0xc000084000)
    /Users/xx/workspace/src/just.for.test/goroutinetest/demo.go:13 +0xbc
created by main.main
    /Users/xx/workspace/src/just.for.test/goroutinetest/demo.go:9 +0x5c
exit status 2

使用defer func()调用recover方法处理子协程中的异常,不让其抛出给父协程

package main

import (
    "fmt"
)

func main() {
    c := make(chan bool)
    go func () {
        defer func() {
            if err:= recover(); err != nil {
                  fmt.Println(err)
            }
        }()
        fmt.Println("go func....")
        c <- true
        close(c)
        c <- true
    }()
    for v:= range c {
        fmt.Println(v)
    }
    fmt.Println("main done...")
}
//===================
go func....
send on closed channel
true
main done...

2.管道(channel)

上面的例子中,我们实际上已经用到了channel他是各协程通信的方式,包含有缓冲和无缓存两种,无缓存的话是阻塞方式 的,就是说,如果读空管道会阻塞直到有数据写入,如果写入非空管道,就会阻塞,直到有数据被读出。有缓冲就像异步的了,只要管道不满就可以一直写,只要管道不为空就可以随时读。

  • 创建channel使用make方法,是否有第二个参数标识channel是否带缓存
  • 读channel使用<- channel语法,写channel使用channel<-语法,
  • 向已满的channel写数据将阻塞,向已空的channel读数据将阻塞
  • 向以关闭的channel读数据,将得到元素类型的零值,向已关闭的channel写数据将抛出异常
  • 在单读单写,单写多读的场景下,最好只由写channel任务关闭channel
  • 在多写场景下,需要使用第三方的协程来管理,这个协程等其他所有写协程都完成后,再关闭channel。
    结合select,可实现循环读取写入管道
package main

import (
    "fmt"
    "time"
    "math/rand"
)

func main() {
    channel := make(chan string)
    rand.Seed(time.Now().Unix())
    go func() {
        cnt := rand.Intn(10)
        fmt.Println("message cnt: ", cnt)
        for i:=0;i<cnt;i++ {
            channel <- fmt.Sprintf("message-%2d", i)
        }
        close(channel)
    }()
    var more bool = true
    var msg string
    for more {
        select{
            case msg, more = <- channel:
                if more {
                    fmt.Println(msg)
                } else {
                    fmt.Println("channel closed")
                }
        }
    }
}

Output:

message cnt:  8
message- 0
message- 1
message- 2
message- 3
message- 4
message- 5
message- 6
message- 7
channel closed

下面看一个问题,下面的例子之所以会出现死锁的情况,是因为range结束的条件是遍历完成channel关闭,如果channel未关闭,其将一直尝试从channel中读取数据,其效果如同向一个空的channel读数据一样

package main

import (
    "fmt"
)

func putchan(ch chan int) {
        ch <- 2
}

func main(){
    ch := make(chan int,12)
    for i:=0;i<5;i++ {
        go putchan(ch)
    }
    for i:= range ch {
        fmt.Println(i)
    }
}

结果:

2
2
2
2
2
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
    /Users/wscn/Desktop/CodeView/chanwirtedo.go:22 +0x10b
exit status 2
wscndeMac-mini:CodeView wscn$ vi chanwirtedo.go

解决方法

package main

import (
    "fmt"
)

func putchan(ch chan int) {
        ch <- 2
}

func main(){
    ch := make(chan int,12)
    for i:=0;i<5;i++ {
        go putchan(ch)
    }
    for i:=0;i<5;i++ {
        fmt.Println(<-ch)
    }
}

上面这种,有多少数据就读多少数据的做法,就不会产生异常。

3.通过不同方式来处理多个channel 通信

  • 带缓存的channel
package main
import (
        "fmt"
        "runtime"
)
// 从 1 至 1 亿循环叠加,并打印结果。
func print(c chan bool, n int) {
        x := 0
        for i := 1; i <= 100000000; i++ {
                x += i
        }
        fmt.Println(n, x)
        c <- false
}

func main() {
    // 使用多核运行程序
        runtime.GOMAXPROCS(runtime.NumCPU())
        //c := make(chan bool, 10)
        c := make(chan bool)
        for i := 0; i < 10; i++ {
                go print(c, i)
        }
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        //<-c
        fmt.Println("DONE.")
}
// =================
4 5000000050000000
false
0 5000000050000000
false
9 5000000050000000
false
1 5000000050000000
false
7 5000000050000000
false
5 5000000050000000
false
2 5000000050000000
false
8 5000000050000000
false
6 5000000050000000
false
3 5000000050000000
false
DONE.
  • 通过waitgroup
package main

import (
    "fmt"
    "runtime"
    "sync"
)

func print(wg *sync.WaitGroup, n int) {
    defer wg.Done()
    x := 0
    for i:=1;i<=100000;i++ {
        x += i
    }
    fmt.Println(n,  x)
}

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    //wg := sync.WaitGroup{}
    var wg sync.WaitGroup
    wg.Add(10)
    for i:=0;i<10;i++ {
        go print(&wg, i)
    }
    wg.Wait()
    fmt.Println("Done.")
}
//= ================
0 5000050000
1 5000050000
9 5000050000
5 5000050000
7 5000050000
3 5000050000
8 5000050000
4 5000050000
6 5000050000
2 5000050000
Done.

4.多channel复用select

多个channel写,读的时候效果如同,多个channel汇聚成一个channel,单从这个channel读数据即可。

package main

import (
    "fmt"
    "time"
    "math/rand"
)

func main() {
    channel := make(chan string)
    rand.Seed(time.Now().Unix())
    go func() {
        cnt := rand.Intn(10)
        fmt.Println("message cnt: ", cnt)
        for i:=0;i<cnt;i++ {
            channel <- fmt.Sprintf("message-%2d", i)
        }
        close(channel)
    }()
    channel2 := make(chan string)
    rand.Seed(time.Now().Unix())
    go func() {
        cnt := rand.Intn(10)
        fmt.Println("message cnt2: ", cnt)
        for i:=0;i<cnt;i++ {
            channel2 <- fmt.Sprintf("message2-%2d", i)
        }
        close(channel2)
    }()
    for {
        select{
            case msg, more := <- channel:
                if more {
                    fmt.Println(msg)
                } else {
                    fmt.Println("channel closed")
                    return
                }
            case msg2,more2 := <- channel2:
               if more2 {
                   fmt.Println(msg2)
               } else {
                   fmt.Println("channel2 colsed")
                   //break
                   // break在这里没有用
                   return
               }
            default:
                fmt.Println("read from default")
        }
    }
}
//=======================
message cnt:  6
message cnt2:  8
read from default
message- 0
message2- 0
message- 1
message2- 1
message- 2
message2- 2
read from default
message2- 3
read from default
message2- 4
read from default
message- 3
message2- 5
message- 4
message2- 6
message2- 7
message- 5
channel closed
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269