goroutine
Goroutine 是 Go 语言支持并发的核心,在一个Go程序中同时创建成百上千个goroutine是非常普遍的,一个goroutine会以一个很小的栈开始其生命周期,一般只需要2KB。区别于操作系统线程由系统内核进行调度, goroutine 是由Go运行时(runtime)负责调度。例如Go运行时会智能地将 m个goroutine 合理地分配给n个操作系统线程,实现类似m:n的调度机制,不再需要Go开发者自行在代码层面维护一个线程池。
在go语言中,每一个并发的执行单元叫做一个goroutine
并发:逻辑上具备同时处理多个任务的能力(一个cpu)
并行:物理上在同一时刻执行多个并发任务(多个cpu)
package main
import (
"fmt"
"sync"
"time"
)
var mymap = make(map[int]int)
// 声明一个全局互斥锁
// lock 是一个全局互斥所
var lock sync.Mutex
func test1(n int) {
res := 1
for i := 1; i < n; i++ {
res *= i
}
lock.Lock()
mymap[n] = res
lock.Unlock()
}
func main() {
for i := 1; i < 21; i++ {
go test1(i)
}
time.Sleep(5 * time.Second)
lock.Lock()
for i, v := range mymap {
fmt.Println(i, v)
}
lock.Unlock()
}
channel
通道channel是用于 goroutine之间的数据传递,通道可用于两个 goroutine
之间通过传递一个指定类型的值来同步运行和通信,避免了显式的锁机制。Go 语言中的通道(channel)是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。每一个通道都是一个具体类型的导管,也就是声明channel的时候需要为其指定元素类型。
package main
import "fmt"
// channle 本质就是一个数据结构-队列
// 数据是先进献出
// 线程安全
// channle是有类型的
// 声明
var chan1 = make(chan int)
var chan2 chan int
// 创建一个可以存放3个int类型的管道
var chan3 = make(chan int, 3)
// 向管道写入数据
func main() {
chan3 <- 10
num := 211
chan3 <- num
//看看管道的长度和容量
fmt.Printf("长度%v,容量%v\n", len(chan3), cap(chan3))
//写入数据不能超过容量
//从管道中读取数据
var num2 int
num2 = <-chan3
fmt.Println(num2)
fmt.Printf("changdu%v,ronglian%v\n", len(chan3), cap(chan3))
}
selet
Go 语言内置了select
关键字,使用它可以同时响应多个通道的操作。
Select 的使用方式类似于之前学到的 switch 语句,它也有一系列 case 分支和一个默认的分支。每个 case 分支会对应一个通道的通信(接收或发送)过程。select 会一直等待,直到其中的某个 case 的通信操作完成时,就会执行该 case 分支对应的语句。
package main
import "fmt"
// 用select解决从管道取数据的堵塞问题
// 传统的方法在遍历管道时,如果不关闭会阻塞而导致deadlock
// 问题 在实际开发中,可能我们不好确定什么时候关闭该管道
// 可以使用select方式来解决
func main() {
intchan1 := make(chan int, 10)
for i := 0; i < 10; i++ {
intchan1 <- i
}
stringchan := make(chan string, 5)
for i := 0; i < 5; i++ {
stringchan <- "hello" + fmt.Sprint(i)
}
for {
select {
case v := <-intchan1:
fmt.Printf("取到数据%的\n", v)
case v := <-stringchan:
fmt.Printf("sting取到%s\n", v)
default:
fmt.Printf("不玩了\n")
return
}
}
}
实例
求1-8000中的素数
package main
import (
"fmt"
"math"
)
// 编译器运行,发现一个管道只有写没有读,则该管道会堵塞
// 判断是否为素数
func isPrime(n int) bool {
if n <= 1 {
return false
}
if n <= 3 {
return true
}
if n%2 == 0 || n%3 == 0 {
return false
}
sqrtN := int(math.Sqrt(float64(n)))
for i := 5; i <= sqrtN; i += 6 {
if n%i == 0 || n%(i+2) == 0 {
return false
}
}
return true
}
func testwrite(intChan chan int) {
for i := 1; i <= 8000; i++ {
intChan <- i
}
close(intChan)
}
func testread(intChan chan int, primeChan chan int, exitChan chan bool) {
for v := range intChan {
if isPrime(v) == true {
primeChan <- v
}
}
for {
v, ok := <-intChan
if !ok {
break
}
fmt.Printf("read data from chan:%d\n", v)
}
exitChan <- true
}
func main() {
intChan := make(chan int, 1000)
primeChan := make(chan int, 10000)
exitChan := make(chan bool, 4)
go testwrite(intChan)
for i := 1; i <= 4; i++ {
go testread(intChan, primeChan, exitChan)
}
for i := 0; i < 4; i++ {
<-exitChan
}
close(primeChan)
close(exitChan)
for v := range primeChan {
fmt.Printf("read data from chan:%d\n", v)
}
}
//使用协程后速度提高