goroutine学习笔记

最简单的goroutine例子

最简单的例子就是直接在main函数中使用go语句生成一个独立的goroutine,如下:

package main

import "fmt"

func main() {  
    go fmt.Println("another goroutine")
    fmt.Println("main goroutine")
}

但是我们执行下,大概率会发现只有输出中只有

main goroutine  

而不是我们想象中的

another goroutine  
main goroutine  

这是因为main函数没等到我们another goroutine执行完毕就退出了。于是我们需要想个方式让main等待another goroutine执行完再退出,当然最简单的方法就是用time.Sleep了:

package main

import (  
    "fmt"
    "time"
)

func main() {  
    go fmt.Println("another goroutine")
    fmt.Println("main goroutine")

    time.Sleep(time.Second)
}

channel

在实战中不太可能会使用time.Sleepmain等待,因为我们不知道生成的独立goroutine什么时候会结束。我们可以用channel来进行maingoroutine的通讯,让goroutine通知main,任务结束了。

package main

import (  
    "fmt"
)

func main() {  
    done := make(chan struct{})
    go func() {
        defer close(done)
        fmt.Println("another goroutine")
    }()
    fmt.Println("main goroutine")

    <-done
}
  1. channel需要用make函数生成,如果直接var done chan struct{}而没有初始化,会panic: close of nil channel
  2. 我们这个例子中channel的作用只是为了传递结果本身,而不是数据,因此可以使用不占内存的结构struct{}
  3. go语句支持匿名函数,要记得go语句调用的是个函数,所以第12行后面要带()
  4. <-done会在main中阻塞,直到在goroutine中关闭channel

(可能的)输出如下:

main goroutine  
another goroutine  

多个goroutine

通常我们需要在main中生成多个goroutine,如果直接将上面的例子加上个for循环,是否可以呢?

package main

import (  
    "fmt"
)

func main() {  
    done := make(chan struct{})
    for i := 0; i < 5; i++ {
        go func() {
             defer close(done)
             fmt.Println("another goroutine")
         }()
    }
    fmt.Println("main goroutine")

    <-done
}

(可能的)输出如下:

main goroutine  
another goroutine  
another goroutine  

可以看到我们用的done通道,在第二个goroutine执行的时候就close掉了,因此main就退出了。

我们修改下程序,让done作为传输数据的通道,每生成一个goroutine,就往done通道中发送1:

package main

import (  
    "fmt"
)

var (  
    count = 5
)

func main() {  
    done := make(chan int)
    for i := 0; i < count; i++ {
        go func() {
            defer func() {
                done <- 1
            }()
            fmt.Println("another goroutine")
         }()
    }
    fmt.Println("main goroutine")

    cnt := 0
    for cnt != count {
        cnt = cnt + <-done
    }
}

sync.WaitGroup

go官方提供了sync.WaitGroup来做上面例子中channel类似的事情,用sync.WaitGroup非常清晰明了:

package main

import (  
    "fmt"
    "sync"
)

var (  
    count = 5
)

func main() {  
    var wg sync.WaitGroup
    for i := 0; i < count; i++ {
        go func() {
            defer wg.Done()
            wg.Add(1)
            fmt.Println("another goroutine")
        }()
    }
    fmt.Println("main goroutine")

    wg.Wait()
}
  1. wg.Add(1):往WaitGroup中增加1;
  2. wg.Done():在WaitGroup中减去1;
  3. wg.Wait():阻塞,直到WaitGroup为0。

生产者消费者模型

main中设置生产者队列,在goroutine中设置消费者队列,在上面的例子上修改如下:

package main

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

var (  
    queueSize = 10
    goroutineCount = 5
)

func main() {  
    var wg sync.WaitGroup
    queue := make(chan int)
    for i := 0; i < goroutineCount; i++ {
        go func() {
            defer wg.Done()
            wg.Add(1)
            for item := range queue {
                 fmt.Printf("queue:%d sleep for %d seconds\n", item, item)
                 time.Sleep(time.Duration(item) * time.Second)
                 fmt.Printf("queue:%d finished\n", item)
            }
        }()
    }
    for j := 0; j < queueSize; j++ {
        queue <- j
    }
    close(queue)

    wg.Wait()
}

在上面的例子中:

    for j := 0; j < queueSize; j++ {
        queue <- j
    }
    close(queue)

是生产者,负责发送数据给队列(通道),而goroutine则是消费者,使用range语句从队列中循环获取消费数据,处理数据:

        go func() {
            defer wg.Done()
            wg.Add(1)
            for item := range queue {
                 fmt.Printf("queue:%d sleep for %d seconds\n", item, item)
                 time.Sleep(time.Duration(item) * time.Second)
                 fmt.Printf("queue:%d finished\n", item)
            }
        }()