这是我参与「第五届青训营 」笔记创作活动的第1天
1.Goroutine
为了更好理解Goroutine,先讲一下线程和协程的概念
线程(Thread) :有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程的切换一般也由操作系统调度。
协程(coroutine) :又称微线程与子例程(或者称为函数)一样,协程(coroutine)也是一种程序组件。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。
和线程类似,共享堆,不共享栈,协程的切换一般由程序员在代码中显式控制。它避免了上下文切换的额外耗费,兼顾了多线程的优点,简化了高并发程序的复杂。
Goroutine和其他语言的协程(coroutine)在使用方式上类似,但从字面意义上来看不同(一个是Goroutine,一个是coroutine),再就是协程是一种协作任务控制机制,在最简单的意义上,协程不是并发的,而Goroutine支持并发的。因此Goroutine可以理解为一种Go语言的协程。同时它可以运行在一个或多个线程上。
Go 程序中使用 go 关键字为一个函数创建一个 goroutine。一个函数可以被创建多个 goroutine,一个 goroutine 必定对应一个函数。
为一个普通函数创建 goroutine 的写法如下:
go 函数名( 参数列表 )
- 函数名:要调用的函数名。
- 参数列表:调用函数需要传入的参数。
例子如下:使用go关键字,将hello函数并发执行
package main
import (
"fmt"
"time"
)
func Hello(i int) {
fmt.Println(i)
}
func main() {
for i := 0; i < 5; i++ {
go func(j int) {
Hello(j)
}(i)
}
time.Sleep(time.Second)
}
打印结果如下:
4
0
2
3
1
2.Channel
Channel即通道,每个通道都有与其相关的类型。该类型是通道允许传输的数据类型。(通道的零值为nil。nil通道没有任何用处,因此通道必须使用类似于map和切片的方法来定义。)
channel有两种形式的,一种是无缓冲的,一个线程向这个channel发送了消息后,会阻塞当前的这个线程,知道其他线程去接收这个channel的消息。无缓冲的形式如下:
intChan := make(chan int)
带缓冲的channel,是可以指定缓冲的消息数量,当消息数量小于指定值时,不会出现阻塞,超过之后才会阻塞,需要等待其他线程去接收channel处理,带缓冲的形式如下:
intChan := make(chan int, 3)
通过channel可以比较方便的实现生产者消费者模型,这里开启一个生产者线程,一个消费者线程,生产者线程往channel中发送消息,同时阻塞,消费者线程轮询获取channel中的消息,
进行处理,然后阻塞,这时生产者线程唤醒继续后面的逻辑,如此便形成了简单的生产者消费者模型。
例子:使用带缓冲的channel和无缓冲的channel进行打印0-9的平方
package main
func main() {
src := make(chan int)
dest := make(chan int, 2)
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}()
for i := range dest {
//操作得到的数据
println(i)
}
}
3.Sync
Go语言的sync包提供了常见的并发编程同步原语
3.1互斥锁-Mutex
同一时刻只能有一个读或写的场景,一个互斥锁只能同时被一个goroutine锁定,其他go程将被阻塞直到互斥锁被解锁,重新争夺互斥锁 使用lock()进行上锁,使用Unlock()进行释放锁
package main
import (
"sync"
"time"
)
var (
x int64
lock sync.Mutex
)
func main() {
x = 0
for i := 0; i < 5; i++ {
go addWithOutLock()
}
time.Sleep(time.Second)
println("WithOutLock -----> ", x)
x = 0
for i := 0; i < 5; i++ {
go addWhitLock()
}
time.Sleep(time.Second)
println("WhitLock -----> ", x)
}
func addWhitLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
func addWithOutLock() {
for i := 0; i < 2000; i++ {
x += 1
}
}
执行结果:
WithOutLock -----> 8286
WhitLock -----> 10000
3.2等待组-WaitGroup
waitGroup.go中有三个常用的方法,分别是Add(int),Done(),Wait()。
1.创建子协程先调用Add增加等待计数
2.子协程结束后调用Done减少协程计数
3.主协程中调用Wait方法进行等待,直到计数器归零继续执行
package main
import (
"fmt"
"sync"
)
func hello(i int) {
println(i)
}
func main() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
fmt.Println("main over")
}