多协程是指一段时间内协程的并行, 即某个任务使用多个协程同时进行处理, 多协程的必要条件: 协程任务之间有关联性 ( 相互之间组成整体任务, 相互之间有连续性 ), 有总分总的协调步骤过程
多协程任务步骤: 任务切分/分配, 启动多个协程, 合并多个协程的结果
多协程使用场景: 运算量比较多的流程上, 协程间依赖性比较弱
多协程的局限: 会增加额外的耗时, 会增加额外的内存消耗
协程等待: sync.WaitGroup
多协程生命周期: 协程的创建等全部生命历程的管理, 作用是为了便于协程的回收利用, 生命周期分类: 协程创建 ( 通过函数前加go关键字 ), 协程回收 ( Go语言的回收机制 ), 协程中断 ( Context 实现中断 )
package main
import (
"context"
"fmt"
"sync"
)
// 协程等待
var wg sync.WaitGroup
// 主函数 主 Goroutine
func main() {
// 初始化一个 context
parent := context.Background()
// 生成一个取消的 context
ctx, cancel := context.WithCancel(parent)
runTimes := 0
wg.Add(1)
// 启动 goroutine
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("Goroutine Done")
return
default:
fmt.Println("Goroutine Running Times: ", runTimes)
runTimes++
}
if runTimes >= 5 {
cancel()
wg.Done()
}
}
}(ctx)
wg.Wait()
}
foo() // 执行函数 foo, 程序等待函数 foo 返回
go foo() // 新建新的 goroutine 去执行函数 foo
bar() // 不用等待函数 foo 返回, 两个函数并发执行
package main
import (
"context"
"fmt"
"sync"
"time"
)
// 协程等待
var wg sync.WaitGroup
// 主函数 主 Goroutine
func main() {
// 资源争抢, 100个人抢10个鸡蛋
eggs := make(chan int, 10) // 设定Channel缓冲区, 根据业务场景需求, 设置适合大小
// 超时 Context
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
// 鸡蛋
for i := 0; i < 10; i++ {
eggs <- i
}
// close(eggs) 关闭后只读不可写, 用for range 可循环获取到通道内的值, 但是用for循环读取时, 即使通道已没有值, 也能获取到对应类型的零值, 一般 close 和 for range 搭配使用
// 100个人并行抢
for i := 0; i < 100; i++ {
wg.Add(1)
go func(n int, c context.Context) {
// 多管道select调度
select {
case egg := <-eggs:
fmt.Printf("people: %d get egg: %d\n", n, egg)
case <-c.Done():
fmt.Println("timeout")
cancel()
return
default:
}
wg.Done()
}(i, ctx)
}
wg.Wait()
}
// 初始化
c := make(chan string) // 创建一个通道 channel, 无缓冲区, 使用时要同时具备输入和输出, 即该管道要同时可写入和读取
go func (){
time.Sleep(1 * time.Second)
// 输入
c <- "message" // 发送消息到 channel
// 关闭
close(c)
}()
// 输出
msg := <- c // 阻塞直到接收到数据
c1 := make(chan string, 1)
c2 := make(chan string, 1)
select {
// c1 和 c2 都有值, 则随机其中之一执行
case v := <-c1: // c1 管道有值则执行该条case
fmt.Println("channel1 sends", v)
case v := <-c2: // c2 管道有值则执行该条case
fmt.Println("channel2 sends", v)
default: // 可选
fmt.Println("neither channel was ready")
}
在 Golang 中没有类和对象的概念, 但是可以通过结构体 struct 和接口 interface 来实现面向对象的三大特性( 封装, 继承, 多态 ), 进而实现面向对象编程
// Foo 定义结构体, 相当于类
type Foo struct {
// 成员属性
baz string
}
// 接收者, 方法
func (f *Foo) echo() {
fmt.Println(f.baz)
}
func main() {
// 初始化结构体, 相当于实例化类
f := Foo{baz: "hello"}
// 调用方法
f.echo()
}
// Foo 定义结构体, 相当于类
type Foo struct {
// 成员属性
baz string
}
// Bar 继承
type Bar struct {
// 拥有Foo所有的属性和方法
Foo
}
// 接收者, 方法
func (f *Foo) echo() {
fmt.Println(f.baz)
}
func main() {
// 初始化结构体, 相当于实例化类
b := Bar{Foo{baz: "chao"}}
// 调用父类的方法
b.echo()
}
// Foo 定义接口, 需实现的方法
type Foo interface {
// 方法
qux()
}
// Bar 结构体
type Bar struct{}
// Baz 结构体
type Baz struct{}
// 实现接口, 只要实现了接口的所有方法, 即实现了该接口
func (b *Baz) qux() {
fmt.Println("baz")
}
func (b *Bar) qux() {
fmt.Println("bar")
}
func main() {
// 定义一个接口类型变量
var f Foo
// 实现了该接口, 即属于该接口类型的变量, 可以直接存到接口变量中
f = &Bar{}
f.qux()
f = &Baz{}
f.qux()
}