本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
通过通信来共享内存,而不是通过共享内存来通信
这句话听起来有些绕口,实际上,由于在各种复杂环境时实现对共享变量的正确访问容易出错,因此go语言鼓励我们用通信来代替共享内存。go语言并发同步模型来自于通信顺序进程CSP(Communicating Sequential Processes)的范型,但在其外在表现上,更加接近于Unix管道的一种泛化。
当然。如果只是对一个整数型变量的引用计数,自然也没必要特意去浪费资源建立通道,只需要对该变量加个互斥的锁即可。
goroutine
Go语言中的goroutine就是这样一种机制,goroutine的概念类似于线程,但 goroutine是由Go的runtime调度和管理的。Go程序会智能地将 goroutine 中的任务合理地分配给每个CPU。Go语言之所以被称为现代化的编程语言,就是因为它在语言层面已经内置了调度和上下文切换的机制。
当你需要让某个任务并发执行的时候,你只需要把这个任务包装成一个函数,开启一个goroutine去执行这个函数就可以了,就是这么简单粗暴。
举个例子
func main(){
runtime.GOMAXPROCS(1)
go func() {
for count := 0; count < 26; count++ {
fmt.Printf("%c ",'a' + count)
}
}()
fmt.Println("main done")
}
输出是:
main done
Process finished with the exit code 0
我们发现,匿名函数里的代码并没有被运行,或者说并没有看到有字母输出,这是因为main函数过早的结束,导致主线程结束运行,因此我们将代码稍稍修改下,让程序多执行一秒钟:
func main(){
runtime.GOMAXPROCS(1)
go func() {
for count := 0; count < 26; count++ {
fmt.Printf("%c ",'a' + count)
}
}()
fmt.Println("main done")
time.Sleep(time.Second)
}
当然了,在实际应用中我们不可能用这种方式来保证一个协程被执行完成。因此,无论是类似的场景还是更实际的多个协程共同协作的场景(例如,多个子task并发完成一部分任务,主task等待他们最后结束。),我们都需要一种方法来保证协程在运行的时候被正确执行。
通过WaitGroup保证同步
先上代码:
func main(){
runtime.GOMAXPROCS(1)
var wg sync.WaitGroup
//
wg.Add(2)
go func() {
// 在函数退出时调用Done来通知main函数工作已经完成。
defer wg.Done()
for count := 0; count < 26; count++ {
time.Sleep(time.Millisecond)
fmt.Printf("%c ",'a' + count)
}
}()
go func() {
defer wg.Done()
for count := 0; count < 26; count++ {
time.Sleep(time.Millisecond)
fmt.Printf("%c ",'A' + count)
}
}()
fmt.Println("main done")
wg.Wait()
}
WaitGroup 对象内部有一个计数器,最初从0开始,它有三个方法:Add(), Done(), Wait() 用来控制计数器的数量。Add(n) 把计数器设置为n ,Done() 每次把计数器-1 ,wait() 会阻塞代码的运行,直到计数器地值减为0。
输出为:
main done
a A B b c C D d e E F f g G H h i I J j k K L l m M N n o O P p q Q R r s S T t u U V v w W X x y Y Z z
Process finished with the exit code 0