GO——学习笔记(九):并发

上篇:GO——学习笔记(八)

下篇:GO——学习笔记(十)

参考:

https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/02.7.md

示例代码——go_8

https://github.com/jiutianbian/golang-learning/blob/master/go_8/main.go

并发

goroutine

大多数语言实现并发,通常通过多线程来实现,golang也类似,但有所不同,具体如下:goroutine是Go并行设计的核心,goroutine是协程,他的粒度要比线程小,可以认为是轻量级的线程,具体什么是协程,请看此网址 https://www.zhihu.com/question/20862617,

执行goroutine只需极少的栈内存,可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。

goroutine 中通过 go 关键字来实现,代码如下

func testgo() {
    go sayhelloMan() //通过go来调用协程执行方法
    go sayhelloWomen()

    time.Sleep(time.Millisecond) //由于goroutine是协程,所以这边如果不加主线程等待时间,main函数会直接结束,不会等待goroutine执行完毕

    fmt.Println("结束")
}

func sayhelloMan() {
    for i := 0; i < 5; i++ {
        fmt.Println("你好,大兄弟")
        time.Sleep(10)
    }
}

func sayhelloWomen() {
    for i := 0; i < 5; i++ {
        fmt.Println("你好,大妹子")
        time.Sleep(10)
    }
}

请注意time.Sleep(time.Millisecond),这段代码,如果不加入这段线程阻塞的方法,那么main函数将会很快执行完便退出了,新开的协程将不会继续执行。

channel

golang中协程间的通信,通过channel来实现,通过make创建出特定的channel类型的值,来发送和接收数据,channel通过操作符<-来接收和发送数据,代码如下:

func send(c chan int, i int) {
    fmt.Println("发送消息:", i)
    c <- i //将值塞到channle中,如果recive方法没有执行,send方法所在线程将阻塞
}

func recive(c chan int) {
    i := <-c
    fmt.Println("接收消息:", i)//将值塞从channle中取出,如果send方法没有执行,recive所在线程将阻塞
}

func testchannel() {
    ch := make(chan int) //定义一个channle,用来接收传递的值

    go send(ch, 10)

    recive(ch)
}

我们上面代码中定义的channle,是无缓冲的channel,无缓冲的channle中数据的存取都是互斥的。拿上面的例子来说,通过send方法,往ch中存入了一个值,如果recive方法没有执行从ch取出值的操作,send所在线程将阻塞,知道recive方法调用。且recive方法,从ch取出值的时候,只有当send方法,往ch中存入了一个值的时候,才能执行,否则会一直等待。

当然,如果对无缓冲的channel只有存取值的操作的其中一个的话,都将会出现死锁,报错 fatal error: all goroutines are asleep - deadlock!

func testchannel() {
    ch := make(chan int) //定义一个channle,用来接收传递的值

    //go send(ch, 10) //如果注释此行或者下面一行中的其中一行,都将会出现死锁报错 fatal error: all goroutines are asleep - deadlock!

    recive(ch) 
}

buffered channel

上面的例子中都是定义的无缓冲的channle,现在定义有缓冲的channle,定义格式如下

ch := make(chan type, value)

其实,无缓冲的channle,是value = 0,当value>1时,就是有缓冲的channle。当读写有缓冲的channle时,直到写满value个元素才阻塞写入。

func testbufferchannel() {
    //ch := make(chan int)
    bufferchannel := make(chan int, 1) //定义缓冲为1的channel
    bufferchannel <- 1                 //可以直接往缓冲的channel中存值,只要小于1
    //bufferchannel <- 2 //大于1,会报死锁的错误
    //ch <- 1 //对于无缓冲的channle直接赋值,会报死锁的错误

    fmt.Println(<-bufferchannel)
}

Range和Close

可以通过range像操作slice或者map一样操作缓存类型的channel

func testrangeandclose() {
    bufferchannel_1 := make(chan int, 10)

    go setNumber(cap(bufferchannel_1), bufferchannel_1)
    for i := range bufferchannel_1 { //通过range取缓冲的channel中的值
        fmt.Println(i)
    }
}

func setNumber(n int, c chan int) {
    for i := 0; i < n; i++ {
        c <- i
    }
    close(c) //通过close关闭channel
}

Select

如果存在多个channel的时候,我们可以通过select监听channel上的数据流动。

select默认是阻塞的,只有当监听的channel中有发送或接收可以进行时才会运行,当多个channel都准备好的时候,select是随机的选择一个执行的。

func testselect() {
    ch := make(chan int)   //定义一个channle,用来接收传递的值
    done := make(chan int) //用来传递完毕的标识

    go speaknumber(ch, done)

    for {
        select {
        case i := <-ch: //当ch准备好值时
            fmt.Println("speack:", i)
        case <-done: //当done准备好值时
            fmt.Println("speack:done")
            return
        }
    }
}

func speaknumber(ch chan int, done chan int) {
    for i := 0; i < 10; i++ {
        ch <- i
    }
    done <- 0
}

在select里面还有default语法,select其实就是类似switch的功能,default就是当监听的channel都没有准备好的时候,默认执行的(select不再阻塞等待channel)。

select {
case i := <-c:
    // use i
default:
    // 当c阻塞的时候执行这里
}

超时

对于上面描述过阻塞超时的情况,我们可以通过select来设置超时来避免.

func testovertime() {
    ch := make(chan int) //定义一个channle,用来接收传递的值

    go saynumber(ch)

    for {
        select {
        case i := <-ch:
            fmt.Println("say:", i)
        case <-time.After(1 * time.Second):
            fmt.Println("say:timeout")
            return
        }
    }
}

func saynumber(ch chan int) {
    for i := 0; i < 10; i++ {
        ch <- i
    }
}

你可能感兴趣的:(GO——学习笔记(九):并发)