后端基础day5 Golang并发 | 青训营笔记

61 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天

协程goroutine

与 传统的系统级线程和进程相比,协程的最大优势在于其“轻量级”,可以轻松创建上百万个而不 会导致系统资源衰竭,而线程和进程通常最多也不能超过1万个。这也是协程也叫轻量级线程的原因。

协程又称为"轻量级线程". 协程的内存一般为kb量级, 比mb量级的线程占用空间小很多. goroutine是Go语言中的轻量级线程实现,由Go运行时(runtime)管理。

Go语言的协程支持非常好, 仅仅需要go关键字即可启动一个goroutine.

func Add(x, y int) {
    z := x + y
    fmt.Println(z)
}
func main() {
    for i := 0; i < 10; i++ {
        go Add(i, i)
    }
}

但是如果运行这段代码, 控制台不会输出任何东西, 原因在于main函数进行完for循环之后就直接退出了, 没有留给十个goroutine执行的机会. 在实践中我也会常常苦恼, 为啥这段并发输出跟我想象的不一样.

看来人脑对并发的支持不够好.

channel

channel是goroutine之间通信的最好方式. 在很多传统语言利用共享内存来实现并发, 而Go的口号是: 不要通过共享内存来通信,而应该通过通信来共享内存

如果用共享内存(此处共享了一个变量) + 加锁的方式实现并发将会非常麻烦.

Go中用更优雅的方式, 即channel实现并发通信

func Count(ch chan int) {
    ch <- 1
    fmt.Println("Counting")
}
func main() {
    chs := make([]chan int, 10)
    for i := 0; i < 10; i++ {
        chs[i] = make(chan int)
        go Count(chs[i])
    }
    for _, ch := range chs {
        <-ch
    }
}

channel的使用

  • 声明chan:
var chanName chan ElementType
var m map[string] chan bool
  • 初始化: (如果在容器中则需要遍历)
ch := make(chan int, 1)   //后面不跟数字代表没有缓冲区
  • 写入与读出: 向channel写入数据通常会导致程序阻塞,直到有其他goroutine从这个channel中读取数据
ch <- value
value := <-ch
  • 单向channel:
var ch1 chan int// ch1是一个正常的channel,不是单向的
var ch2 chan<- float64// ch2是单向channel,只用于写float64数据
var ch3 <-chan int// ch3是单向channel,只用于读取int数据

select

每一个case都必须包含一个IO操作, 所以select可以用于处理异步IO问题.

select {
    case <-chan1:
        // 如果chan1成功读到数据,则进行该case处理语句
    case chan2 <- 1:
        // 如果成功向chan2写入数据,则进行该case处理语句
    default:
        // 如果上面都没有成功,则进入default处理流程
}