Go 语言入门:goroutine和channel|青训营

63 阅读1分钟

实现高并发:

协程:用户态,轻量级线程,栈kb级别

go 函数名():开启一个协程

线程:内核态,线程包含多个协程,栈mb级别

func newTask(){  
    i:=0
    for{
        i++
        fmt.Println("new Goroutine : i= %d\n",i)
        time.Sleep(1*time.Second)
    }
}
​
func main(){
    //创建协程
    go newTask()
}

用go承载一个形参为空,返回值为空的函数

package main
 
import (
    "fmt"
    "runtime"
    "time"
)
 
func main() {
    go func() {
        defer fmt.Println("A.defer")
        
        func() {
            defer fmt.Println("B.defer")
       
            fmt.Println("B") 
        }()
        
        fmt.Println("A") 
    }()       //不要忘记() 匿名函数}后面要加一对小括号
 
    //死循环,目的不让主goroutine结束
    for {
        time.Sleep(1*time.Second)
    }
}
​

输出结果: B B.defer A A.defer

func main() {
    go func() {
        defer fmt.Println("A.defer")
        
        func() {
            defer fmt.Println("B.defer")
            //退出当前的goroutine
            //调用 runtime.Goexit() 将立即终止当前 goroutine 执⾏,调度器确保所有已注册 defer 延迟调用被执行。
            runtime.Goexit()
            fmt.Println("B") 
        }()
        
        fmt.Println("A") 
    }()       
​
    for {
        time.Sleep(1*time.Second)
    }
}

输出结果: B.defer A.defer

用go承载一个有形参,有返回值的函数

func main(){
    go func(a int ,b int) bool{
        fmt.Println("a=",a,",b=",b)
        return true
    }(10,20)
}

输出结果:a=10,b=20

channel

协程间的通信:提倡通过通信实现共享内存而不是通过共享内存实现通信

image.png

channel <- value      //发送value到channel
<-channel             //接收并将其丢弃
x := <-channel        //从channel中接收数据,并赋值给x
x, ok := <-channel    //功能同上,同时检查通道是否已关闭或者是否为空

make(chan 元素类型,[缓冲大小])

func main(){
    c := make(chan int)
 
    go func() {
        defer fmt.Println("子go程结束")
 
        fmt.Println("子go程正在运行……")
 
        c <- 666 //666发送到c
    }()
 
    num := <-c //从c中接收数据,并赋值给num
 
    fmt.Println("num = ", num)
    fmt.Println("main go程结束")   
    }
}
输出:
子go程正在运行……
子go程结束
num=666
main go程结束

无缓冲 :通信双方必须要确保对方读取到数据才能退出通道,去做其他事情。

22-channel2.png

有缓冲 :收发数据不受对方影响。发送完数据后不管接收方有没有读取都可以去做其他事。

24-channel4.png

func main(){
    c := make(chan int)
    go func() {
        defer fmt.Println("子go程结束")

        fmt.Println("子go程正在运行……")

        c <- 666 //666发送到c
    }()

    num := <-c //从c中接收数据,并赋值给num

    fmt.Println("num = ", num)
    fmt.Println("main go程结束")   
    }
 }

输出: 子go程正在运行…… 子go程结束 num=666 main go程结束

25-channel5.png

当channel已满,再向里面写数据,就会阻塞

当channel为空,从里面取数据也会阻塞

关闭channel

package main
 
import (
    "fmt"
)
 
func main() {
    c := make(chan int)
 
    go func() {
        for i := 0; i < 5; i++ {
            c <- i
        }
        close(c) //关闭channel
    }()
 
    for {
        //ok为true说明channel没有关闭,为false说明管道已经关闭
        if data, ok := <-c; ok {
            fmt.Println(data)
        } else {
            break
        }
    }
    fmt.Println("Finished")
}
  • channel不像文件一样需要经常去关闭,只有当确实没有数据发送,或者想显式的结束range循环之类的,才去关闭channel;
  • 关闭channel后,无法向channel 再发送数据(引发 panic 错误后导致接收立即返回零值);
  • 关闭channel后,可以继续从channel接收数据;
  • 对于nil channel,无论收发都会被阻塞。

channel与range

可以使用range来迭代不断操作channel

for data := range c {
    fmt.Println(data)
}

channel与select

单流程下一个go只能监控一个channel的状态,select可以完成监控多个channel的状态。

select {
case <- chan1:
    // 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
    // 如果成功向chan2写入数据,则进行该case处理语句
default:
    // 如果上面都没有成功,则进入default处理流程
}
package main
 
import  "fmt"func fibonacci(c, quit chan int) {
    x, y := 1, 1
    for {
        select {
        case c <- x:
            //如果通道c可写,就执行下面语句
            x, y = y, x+y
        case <-quit:
            //如果通道quit可读,就执行下面语句
            fmt.Println("quit")
            return
        }
    }
}
 
func main() {
    c := make(chan int)
    quit := make(chan int)
 
    go func() {
        for i := 0; i < 6; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
 
    fibonacci(c, quit)
}