本文适合初学者阅读
- 只须在函数调用前添加
go
关键字即可创建并发任务. - 关键字
go
并非执行并发操作, 而是创建一个并发任务单元. - 与defer一样, goroutine也会因"延迟执行"而立即计算并复制执行参数.
package main
import (
"log"
"time"
)
var c int
func counter() int {
c++
return c
}
func main() {
a := 100
go func(x, y int) {
time.Sleep(time.Second)
log.Println("go:", x, y)
}(a, counter())
a += 100
log.Println("main:", a, counter())
time.Sleep(time.Second * 3)
}
wait
- 进程退出时,不会等待并发任务结束, 可用通道(channel)阻塞, 然后发出退出信号.
package main
import (
"log"
"time"
)
func main() {
exit := make(chan struct{})
go func() {
time.Sleep(time.Second)
log.Println("goroutine done.")
close(exit)
}()
log.Println("main ...")
<-exit
log.Println("main exit.")
}
- 若要等待多个任务结束, 推荐使用
sync.WaitGroup
, 通过设定计数器, 让每个goroutine在退出前递减, 直至归零时解除阻塞.
package main
import (
"log"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
time.Sleep(time.Second)
log.Println("goroutine", id, "done.")
}(i)
}
log.Println("main...")
wg.Wait()
log.Println("main exit.")
}
- 尽管
WaitGroup.Add
实现了原子操作, 但建议在goroutine外累加计数器, 以免Add尚未执行, wait
已经退出
package main
import (
"log"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
wg.Add(1)
go func() {
wg.Wait()
log.Println("wait exit.")
}()
go func() {
time.Sleep(time.Second)
log.Println("done.")
wg.Done()
}()
wg.Wait()
log.Println("main exit.")
}
- GOMAXPROCS
- 运行时可能会创建很多线程, 但任何时候仅有限的几个线程参与并发任务执行. 该数量与处理器核数相等, 可用runtine.GOMAXPROCS函数(或环境变量)修改.
package main
import (
"log"
"math"
"runtime"
"sync"
)
func count() {
x := 0
for i := 0; i < math.MaxUint32; i++ {
x += i
}
log.Println(x)
}
func test(n int) {
for i := 0; i < n; i++ {
count()
}
}
func test2(n int) {
var wg sync.WaitGroup
wg.Add(n)
for i := 0; i < n; i++ {
go func() {
count()
wg.Done()
}()
}
wg.Wait()
}
func main() {
n := runtime.GOMAXPROCS(0)
test(n)
}
local storage
- 与线程不同, goroutine 任务无法设置优先级, 无法获取编号, 没有局部存储, 甚至连返回值都会被抛弃, 但除优先级外, 其他功能都很容易实现.
Gosched
- 暂停, 释放线程去执行其他任务. 当前任务被放回队列, 等待下次调试时恢复执行.
- 该函数很少被使用, 因为运行时会主动向长时间运行的任务发出抢占调度
Goexit
- 立即终止当前任务, 运行时确保所有已注册延迟调用被执行. 该函数不会影响其他并发任务, 不会引发panic, 自然也无法捕获
- 如果在main.mian里调用Goexit, 它会等待其他任务结束,然后让进程直接崩溃.
- 无论身处哪一层, Goexit都能立即终止整个调用堆栈, 这与return仅退出当前函数不同.
- 标准库函数
os.Exit
可终止进程, 但不会执行延迟调用.