Golang并发介绍
操作系统会在物理处理器上调度线程来运行,而 Go 语言的运行时会在逻辑处理器上调度goroutine
来运行。每个逻辑处理器都分别绑定到单个操作系统线程。这些逻辑处理器会用于执行所有被创建的goroutine。即便只有一个逻辑处理器,Go也可以以神奇的效率和性能,并发调度无数个goroutine。
goroutine示例
package main
import (
"fmt"
"runtime"
"sync"
)
func main() {
// 分配一个逻辑处理器给调度器使用
runtime.GOMAXPROCS(1)
// runtime.GOMAXPROCS(runtime.NumCPU())
// wg 用来等待程序完成
// 计数加 2,表示要等待两个 goroutine
var wg sync.WaitGroup
wg.Add(2)
fmt.Println("Start Goroutines")
// 声明一个匿名函数,并创建一个 goroutine
go func() {
// 在函数退出时调用 Done 来通知 main 函数工作已完成
defer wg.Done()
// 显示字母表三次
for count := 0; count < 3; count++ {
for char := 'a'; char < 'a'+26; char++ {
fmt.Printf("%c ", char)
}
}
}()
// 声明一个匿名函数,并创建一个 goroutine
go func() {
// 在函数退出时调用 Done 来通知 main 函数工作已完成
defer wg.Done()
// 显示字母表三次
for count := 0; count < 3; count++ {
for char := 'A'; char < 'A'+26; char++ {
fmt.Printf("%c ", char)
}
}
}()
// 等待 goroutine 结束
fmt.Println("Waiting To Finish")
wg.Wait()
fmt.Println("\nTerminating Program")
}
Start Goroutines
Waiting To Finish
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N
O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b
c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p
我们以用go
关键字并且以匿名函数形式创建了两个goroutine
,让他们并发运行,分别打印大写字母表和小写字母表三次。打印大写字母表的goroutine
率先抢到了逻辑处理器使用权,并且在切换前就已经打印完毕,所以我们没有看到大小写字母交替出现的情况。
runtime.GOMAXPROCS(1)
这个函数允许程序更改调度器可以使用的逻辑处理器的数量,给这个函数传入 1,是通知调度器只能为该程序使用一个逻辑处理器。
// wg 用来等待程序完成
// 计数加 2,表示要等待两个 goroutine
var wg sync.WaitGroup
wg.Add(2)
// 声明一个匿名函数,并创建一个 goroutine
go func() {
// 在函数退出时调用 Done 来通知 main 函数工作已完成
defer wg.Done()
/*...*/
}
}()
// 等待 goroutine 结束
wg.Wait()
WaitGroup
是一个计数信号量,可以用来记录并维护运行的 goroutine
。如果 WaitGroup
的值大于 0,Wait
方法就会阻塞。
当某个goroutine
完成时,会调用wg.Done()
表示函数完成,wg
信号量减少1,直到wg == 0
,并最终释放main
函数,让它不再等待,即不再阻塞于wg.Wait()
处。
关键词defer
会修改函数调用时机,可在函数结束前调用相应函数。在网络编程中关闭一些套接字中会很方便。