概述
go在语言层面对并发编程提供支持,通过goroutine(协程)机制;goroutine 是一种非常轻量级的实现,可在单个进程里执行成千上万的并发任务,只需要在函数调用语句前添加go 关键字,即可创建并发执行单元;
有人把Go 比作21世纪的C语言,第一是因为Go语言设计简单,第二是21世纪最重要的就是并发程序设计,而Go从语言层面就支持了并发,同时,并发程序的内存管理有时候是非常复杂的,而Go语言提供了自动垃圾回收机制
Go 语言为并发编程而内置的上层API基于CSP(COMMUNICATING SEQUENTIAL PROCESSES, 顺序通信进程)模型,这就意味着显示锁都是可以编码的。因为Go 语言通过安全的通道发送和接收数据以实现同步,大大地简化了并发程序的编写
一般情况下,一个普通的桌面计算机跑10----20几个线程就有点负载过大了,但是同样一台机器却可以轻松地让成百上千甚至过万个goroutine 进行资源竞争
gouroutine
定义
goroutine 是Go语言并发设计的核心。goroutine 说到底其实就是协程,它比线程更小,十几个goroutine 可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine 之间的内存共享,执行goroutine 只需极少的栈内存(4---5KB),当然会根据相应的数据伸缩;也正因为如此,可同时运行成千上万个并发任务。goroutine比thread 更易用、更高效、更轻便。
创建goroutine
import (
"fmt"
"runtime"
"time"
)
func main() {
//创建 goroutine, 两个死循环同时执行
go newTask()
for {
fmt.Println("this is a main goroutine")
time.Sleep(time.Second)
}
}
func newTask() {
for {
fmt.Println("this is a newTask goroutine")
time.Sleep(time.Second)
}
}
执行结果如下:
~/work_space/go_works/src/admin/concurrent » go run concurrent.go ryanxu@iterm
this is a main goroutine
this is a newTask goroutine
this is a newTask goroutine
this is a main goroutine
this is a main goroutine
this is a newTask goroutine
this is a main goroutine
this is a newTask goroutine
this is a newTask goroutine
this is a main goroutine
this is a main goroutine
this is a newTask goroutine
^Csignal: interrupt
主协程退出了, 其他子协程也会跟着退出
import (
"fmt"
"time"
)
func main() {
go func() {
i := 0
for {
i++
fmt.Println("子协程 i = ", i)
time.Sleep(time.Second)
}
}()
i := 0
for {
i++
fmt.Println("主协程 i = ", i)
time.Sleep(time.Second)
if i == 2 {
break
}
}
}
执行结果如下:
------------------------------------------------------------
~/work_space/go_works/src/admin/concurrent » go run concurrent.go ryanxu@iterm
主协程 i = 1
子协程 i = 1
子协程 i = 2
主协程 i = 2
------------------------------------------------------------
~/work_space/go_works/src/admin/concurrent »
当主协程(main 函数)中 i == 2 , break 跳出循环, 主函数执行完时, 子协程也跟着退出,无法继续执行
runtime 包
runtime.Gosched()
runtime.Gosched() 用于让出CPU时间片,让出当前goroutine 的执行权限,调度器安排其他等待的任务运行,并在下一次某个时候从该位置恢复执行
类似接力赛,A跑了一会儿碰到代码runtime.Gosched() 就把接力棒交给B, 然后A 歇着了B继续跑
import (
"fmt"
"runtime"
"time"
)
func main() {
go func() {
for i := 0; i < 5; i++ {
fmt.Println("hello go")
}
}()
for i := 0; i < 2; i++ {
//runtime.Gosched() 让出时间片,先让别的协程执行,待其执行完,再回来执行此协程
runtime.Gosched()
fmt.Println("hello")
}
}
执行结果如下:
------------------------------------------------------------
~/work_space/go_works/src/admin/concurrent » go run concurrent.go ryanxu@iterm
hello go
hello go
hello go
hello go
hello go
hello
hello
runtime.Goexit()
import (
"fmt"
"runtime"
"time"
)
func main() {
go func() {
fmt.Println("aaaaaaaaaaaaaaa")
//调用别的函数
test()
fmt.Println("bbbbbbbbbbbbbbb")
}()
//特地写一个死循环,目的不让主协程结束
for {
}
}
//区分一下 return 和 runtime.Goexit() 的区别
func test() {
defer fmt.Println("ccccccccccccccc")
//return 终止此函数
runtime.Goexit() //终止所在的协程
fmt.Println("ddddddddddddddd")
}
执行结果如下:
~/work_space/go_works/src/admin/concurrent » go run concurrent.go ryanxu@iterm
aaaaaaaaaaaaaaa
ccccccccccccccc
^Csignal: interrupt
runtime.GOMAXPROCS()
import (
"fmt"
"runtime"
"time"
)
func main() {
// n := runtime.GOMAXPROCS(1)
n := runtime.GOMAXPROCS(4)
fmt.Println("n = ", n)
for {
go fmt.Print(1)
fmt.Print(0)
}
}
修改注释代码,设置1 和设置为4 时, 根据不同的输出结果很容易看得出结果
多任务竞争问题
import (
"fmt"
"runtime"
"time"
)
func main() {
go person1()
go person2()
//特意不让主函数结束
for {
}
}
//定义一个打印机,参数为字符串,按每个字符打印; 打印机属于公共资源
func Printer(str string) {
for _, data := range str {
fmt.Printf("%c", data)
time.Sleep(time.Second)
}
fmt.Println()
}
//定义一个channel
var ch = make(chan int)
func person1() {
Printer("hello")
ch <- 111 //给管道写数据, 发送
}
func person2() {
<-ch // 从管道获取数据, 接收,如果通道没有数据,则会阻塞
Printer("world")
}
输出结果如下:
------------------------------------------------------------
~/work_space/go_works/src/admin/concurrent » go run concurrent.go ryanxu@iterm
hello
world
^Csignal: interrupt
------------------------------------------------------------
~/work_space/go_works/src/admin/concurrent »
这个地方用了channel 来进行通信,在person1 中未向管道ch 中发送数据时, person2 中不能接受到数据,此时person2阻塞,继而保证先完整的输出hello ,再去输出world ;当然,测试过程中,可以将 channel 对象 ch 的所在的三行代码注释掉,然后再看执行效果, 因为多任务编程,确实比较抽象。
举个现实例子:
Tom 和 John 两个人都要使用打印机打印文件, 正常的逻辑是, Tom 打印完了, 然后告诉John,John 你可以来打印了; 而这个告诉的机制就是程序中的channel (通道);
BTW: 下一篇会详细介绍channel