1.runtime包 (续)
runtime.GOMAXPROCS
Go运行时的调度器使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行Go代码。默认值是机器上的CPU核心数。例如在一个8核心的机器上,调度器会把Go代码同时调度到8个OS线程上(GOMAXPROCS是m:n调度中的n)。
Go语言中可以通过runtime.GOMAXPROCS()函数设置当前程序并发时占用的CPU逻辑核心数。
Go1.5版本之前,默认使用的是单核心执行。Go1.5版本之后,默认使用全部的CPU逻辑核心数。
我们可以通过将任务分配到不同的CPU逻辑核心上实现并行的效果,这里举个例子:
Go
package main
import (
"fmt"
"runtime"
"time"
)
func a(i int) {
fmt.Println("A", i)
}
func b(i int) {
fmt.Println("B", i)
}
func main() {
runtime.GOMAXPROCS(1)
for i := 0; i < 5; i++ {
go a(i)
go b(i)
}
time.Sleep(time.Second * 2)
}
Go
package main
import (
"fmt"
"runtime"
"time"
)
func a(i int) {
fmt.Println("A", i)
}
func b(i int) {
fmt.Println("B", i)
}
func main() {
// 逻辑核心数设置为2
runtime.GOMAXPROCS(2)
for i := 0; i < 10; i++ {
go a(i)
go b(i)
}
time.Sleep(time.Second * 2)
}
可以看到兩種結果的對比,第一种依然按照函数的顺序执行,BABABA交叉执行,第二种当程序分配了两个内核之后,执行顺序就会是并行状态。
Go语言中的操作系统线程和goroutine的关系:
- 1.一个操作系统线程对应用户态多个goroutine。
- 2.go程序可以同时使用多个操作系统线程。
- 3.goroutine和OS线程是多对多的关系,即m:n。
2.Channel-协程间的通信机制
单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。
虽然可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题。为了保证数据交换的正确性,必须使用互斥量对内存进行加锁,这种做法势必造成性能问题。
Go语言的并发模型是CSP(Communicating Sequential Processes),提倡通过通信共享内存而不是通过共享内存而实现通信。
如果说goroutine是Go程序并发的执行体,channel就是它们之间的连接。channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。
Go 语言中的通道(channel)是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。每一个通道都是一个具体类型的导管,也就是声明channel的时候需要为其指定元素类型。
Go
package day12
import "fmt"
func D121() {
// 建立channel变量
var ch chan int //声明一个传递int型的通道
var ch1 chan string
var ch2 chan []int // 切片类型
fmt.Println("空channel的值为:", ch)
// 使用make初始化
// make(chan 元素类型, [缓冲大小])
chn := make(chan int)
// 发送
chn <- 10
// 接收
tmp := <-chn
// 关闭通道
close(chn)
}
2.3无缓冲通道 (同步通道)
对于一个无缓冲的通道,可以看成是os的PV操作,只有先P才可以V,先接受才可以发送
Go
package day12
import "fmt"
func D122() {
ch1 := make(chan int)
ch1 <- 10
fmt.Println(ch1)
}
goroutine启动接收才可以使用无缓冲通道发送
Go
package day12
import "fmt"
func D122() {
ch1 := make(chan int)
// 启用goroutine从通道接收值
go recv(ch1)
ch1 <- 10
fmt.Println("发送成功", &ch1)
}
func recv(c chan int) {
a := <-c
fmt.Println("接收成功", a)
}
无缓冲通道上的发送操作会阻塞,直到另一个goroutine在该通道上执行接收操作,这时值才能发送成功,两个goroutine将继续执行。相反,如果接收操作先执行,接收方的goroutine将阻塞,直到另一个goroutine在该通道上发送一个值。
使用无缓冲通道进行通信将导致发送和接收的goroutine同步化。因此,无缓冲通道也被称为同步通道。
2.4有缓冲通道
就是通道有了缓冲容量,遵循队列先进先出
Go
package day12
import "fmt"
func D123() {
ch1 := make(chan int, 1)
ch1 <- 20
fmt.Println("缓冲通道大小为1,发送成功")
}
2.5 从通道取值
Go
package day12
import "fmt"
func D124() {
// 声明无缓冲通道
ch1 := make(chan int)
ch2 := make(chan int)
// 开启goroutine将0~100的数发送到ch1中
go func() {
for i := 0; i < 100; i++ {
ch1 <- i
}
close(ch1)
}()
go func() {
// 开启goroutine从ch1中接收值,并将该值的平方发送到ch2中
for {
i, ok := <-ch1 //通道关闭后再取值ok=false
if !ok {
fmt.Println("通道关闭")
break
}
ch2 <- i * i
}
close(ch2)
}()
// 在主goroutine中从ch2中接收值打印
for i := range ch2 { // 通道关闭后会退出for range循环
fmt.Println(i)
}
}