目录

一 goroutine

1.1 goroutine 需要合理退出

Goroutine经常用来处理耗时操作,但是如果无限制创建协程,不考虑协程的退出和生命周期,容易造成goroutine的失控:

    ch := make(chan int)
    for {

        var dummy string
        fmt.Scan(&dummy)

        go func(ch chan int) {                // 模拟耗时操作
            for {
                data := <-ch
                fmt.Println(data)
            }
        }(ch)

        fmt.Println(runtime.NumGoroutine())
    }

程序运行后,随着输入的字符串越来越多,goroutine将会无限制地被创建,但并不会结束,为了避免这种情况,可以为协程添加退出条件:

    ch := make(chan int)
    for {

        var dummy string
        fmt.Scan(&dummy)

        // 设置强行退出条件,方式协程内部的退出条件无法触发
        if dummy == "quit" {
            for i := 0; i < runtime.NumGoroutine(); i++ {
                ch <- 0
            }
            continue
        }

        go func(ch chan int) {                // 模拟耗时操作
            for {
                data := <-ch
                if data == 0{                // 添加协程内部退出条件
                    break
                }
                fmt.Println(data)
            }
        }(ch)

        fmt.Println(runtime.NumGoroutine())
    }

1.2 不能滥用goroutine

为了保证多个 goroutine 并发访问的安全性,通道也需要做一些锁操作,因此通道其实并不比锁高效。对于 TCP 来说, 一般是接收过程创建 goroutine 并发处理 。当套接字结束时,就要正常退出这些 goroutine。

package main

import (
    "fmt"
    "net"
    "time"
)

func socketRecv(conn net.Conn, exitChan chan string) {
    buf := make([]byte, 1024)
    for {                                // 创建循环不停接收数据
        _, err := conn.Read(buf)        // 从套接字中读取数据
        if err != nil {
            break
        }
    }
    exitChan <- "exit"
}

func main() {
    conn, err := net.Dial("tcp", "www.163.com:80")
    if err != nil {
        fmt.Println(err)
        return
    }

    exit := make(chan string)
    go socketRecv(conn, exit)
    time.Sleep(time.Second)
    conn.Close()                // 主动关闭套接字,触发err
    fmt.Println(<-exit)

}

修改后的例子如下:

package main

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

func socketRecv(conn net.Conn, wg *sync.WaitGroup) {
    buf := make([]byte, 1024)
    for {                                // 创建循环不停接收数据
        _, err := conn.Read(buf)        // 从套接字中读取数据
        if err != nil {
            break
        }
    }
    wg.Done()
}

func main() {
    conn, err := net.Dial("tcp", "www.163.com:80")
    if err != nil {
        fmt.Println(err)
        return
    }

    var wg sync.WaitGroup
    wg.Add(1)
    go socketRecv(conn, &wg)

    time.Sleep(time.Second)
    conn.Close()                // 主动关闭套接字,触发err

}