一, goroutine
与传统的系统级线程和进程相比, 协程的大优势在于其 "轻量级", 可以轻松创建上百万个而不会导致系统资源衰竭, 而线程和进程通常多也不能超过 1 万个. 这也是协程也叫轻量级线程的原因.
golang 原生支持并发编程
轻量级线程
非抢占式多任务处理, 由协程主动交出控制权
编译器 / 解释器 / 虚拟机层面的多任务
多个协程可能在一个或多个线程上运行
1.1 go 对协程的实现
goroutine--Go 对协程的实现
go + 函数名: 启动一个协程执行函数体
- package main
- import (
- "fmt"
- "time"
- )
- func test_Routine() {
- fmt.Println("This is one routine!!!")
- }
- func Add(x, y int) {
- z := x + y
- fmt.Println(z)
- }
- func main() {
- for i := 1; i < 10; i++ {
- // 启动一个协程执行函数体
- go Add(i, i)
- }
- // 为避免并发执行后程序立即退出, 先 sleep 2 秒
- time.Sleep(2)
- }
- package main
- import (
- "fmt"
- "runtime"
- "time"
- )
- func main() {
- // 定义一个数组
- var a [10]int
- // 循环并发执行匿名函数, 实现
- for i := 0; i < 10; i ++ {
- go func(i int) {
- for {
- a[i]++
- // 主动让 go 协程让出时间片
- runtime.Gosched()
- }
- }(i)
- }
- time.Sleep(time.Millisecond)
- fmt.Println(a)
- }
二, channel
Go 语言在语言级别提供的 goroutine 间的通信方式
不要通过共享来通信, 而要通过通信来共享.
channel 的读写默认是阻塞的, 除非有 goroutine 对其进行操作.
- package main
- import (
- "fmt"
- "strconv"
- )
- // 定义一个加法函数, 传入 x,y 整型参数, quit 整型通道
- func Add(x, y int, quit chan int) {
- z := x + y
- fmt.Println(z)
- // 发送 1 到 channel quit
- quit <- 1
- }
- // 读取 channel 中的数据
- func Read(ch chan int) {
- // 将 channel 中数据发送出去, 赋值给 value
- value := <-ch
- fmt.Println("value:" + strconv.Itoa(value))
- }
- // 写数据到 channel 中
- func Write(ch chan int) {
- //ch <- 10
- }
- func main() {
- //ch := make(chan int)
- //go Read(ch)
- //go Write(ch)
- //time.Sleep(10)
- //fmt.Println("end of code")
- // 定义一个容量为 10 的非阻塞整型通道切片, 变 量名为 chs
- chs := make([]chan int, 10)
- // 循环地给 channel 切片 chs 初始化
- for i := 0; i < 10; i++ {
- chs[i] = make(chan int)
- go Add(i, i, chs[i])
- }
- // 遍历 channel 切片 chs, 并从 channel 中发出数据, 留空
- for _, v := range chs {
- <-v
- }
- }
三, 缓冲 channel
定义: c = make(chan int, n) n 为缓冲区的大小, 代表 channel 可以存储多少个元素, 这几个元素可以无阻塞的写入, 缓存的元素写满之后阻塞, 除非有 goroutine 操作.
例子中定义一个容量为 2 的 channel,
- // 缓冲 channel
- package main
- import (
- "fmt"
- "time"
- )
- // 定义一个 chnnel 类型变量 ch
- var ch chan int
- // 测试 buffered channel 函数
- func test_channel() {
- // 第一次发送常量 1 到 channel ch
- ch <- 1
- fmt.Println("ch 1")
- // 第二次发送常量 1 到 channel ch
- ch <- 1
- fmt.Println("ch 2")
- // 第三次发送常量 1 到 channel ch
- ch <- 1
- fmt.Println("come to end goroutine 1")
- }
- func main() {
- ch = make(chan int, 0) // 等价于 ch = make(chan int) 都是不带缓冲的 channel
- ch = make(chan int, 2) // 是带缓冲的 channel
- go test_channel()
- time.Sleep(2 * time.Second)
- fmt.Println("running end!")
- <-ch
- time.Sleep(time.Second)
- }
- output:
- ch 1
- ch 2
- running end!
- come to end goroutine 1
- package main
- import "fmt"
- func main() {
- c := make(chan int, 3 )// 修改 2 为 1 就报错, 修改 2 为 3 可以正常运行
- c <- 1
- c <- 2
- fmt.Println(<-c)
- fmt.Println(<-c)
- }
四, select
Linux 很早就引入的函数, 用来实现非阻塞的一种方式. Go 语言直接在语言级别支持 select 关键字, 用于处理异步 IO 问题. 我们上面介绍的都是只有一个 channel 的情况, 那么如果存在多个 channel 的时候, 我们该如何操作呢, Go 里面提供了一个关键字 select, 通过 select 可以监听 channel 上的数据流动.
select 默认是阻塞的, 只有当监听的 channel 中有发送或接收可以进行时才会运行, 当多个 channel 都准备好的时候, select 是随机的选择一个执行的.
- package main
- import (
- "fmt"
- "time"
- )
- func main() {
- ch := make(chan int)
- // 匿名函数, 传入一个参数整型 channel 类型 ch
- go func(ch chan int) {
- ch <- 1
- }(ch)
- time.Sleep(time.Second)
- select {
- // 如果 ch 成功读到数据, 则执行下面的语句
- case <-ch:
- fmt.Print("come to read ch!")
- default:
- fmt.Print("come to default!")
- }
- }
- // 实现超时控制
- package main
- import (
- "fmt"
- "time"
- )
- func main() {
- ch := make(chan int)
- // 定义一个 channel timeout
- timeout := make(chan int, 1)
- // 定义一个匿名函数, 用来实现超时控制
- go func() {
- time.Sleep( time.Second)
- timeout <- 1
- }()
- select {
- case <-ch:
- fmt.Print("come to read ch!\n")
- case <-timeout:
- fmt.Print("come to timeout!\n")
- }
- fmt.Print("end of code!")
- }
- // 使用 time.After(time.Second) 实现控制
- package main
- import (
- "fmt"
- "time"
- )
- func main() {
- ch := make(chan int)
- select {
- case <-ch:
- fmt.Print("come to read ch!\n")
- case <-time.After(time.Second):
- fmt.Print("come to timeout!\n")
- }
- fmt.Print("end of code!")
- }
- // goroutine_2.go
- package main
- import (
- "fmt"
- "runtime"
- "strconv"
- "time"
- )
- func main() {
- // 协程 1
- go func() {
- for i := 1; i < 100; i++ {
- if i == 10 {
- // 主动出让 CPU 使用的话 需要 导入 runtime 包
- runtime.Gosched()
- }
- fmt.Println("routine 1:" + strconv.Itoa(i))
- }
- }()
- // 协程 2
- go func() {
- for i := 100; i < 200; i++ {
- fmt.Println("routine 2:" + strconv.Itoa(i))
- }
- }()
- time.Sleep(time.Second)
- }
来源: http://www.bubuko.com/infodetail-2846202.html