协程和管道
协程和管道
协程 goroutine
特点
-
有独立的栈空间
-
共享程序堆空间
-
调度由用户控制
-
协程是轻量级的线程
package main
import (
"fmt"
"strconv"
"time"
)
//在主线程每隔一秒输出hello,world!
//在主线程每隔一秒输出hello,world!退出程序
//要求主线程和goroutine
func test(){
for i:=1;i<=10;i++{
fmt.Println(" test hello,world+" +strconv.Itoa(i))
time.Sleep(time.Second)
}
}
func main() {
go test()//开启一个协程
for i:=1;i<=10;i++{
fmt.Println(" main hello,world+" +strconv.Itoa(i))
time.Sleep(time.Second)
}
}
MPG模式
-
M:操作系统上的主线程
-
P:协程执行需要的上下文
-
G:协程
设置cpu数目
package main
import (
"fmt"
"runtime"
)
func main() {
cpuNum := runtime.NumCPU()
fmt.Println("cpuNum=",cpuNum)
//可以直接设置使用多个cpu
runtime.GOMAXPROCS(cpuNum-1)
fmt.Println("ok,ccpuNum=",cpuNum)
}
用例
package main
import (
"fmt"
"time"
)
//1-200的各个数的阶乘,并把每个数放到map中,使用协程
var (
myMap = make(map[int]int,10)
)
func test(n int){
res := 1
for i:=1;i<=n;i++{
res *= i
}
//将res放入到map中
myMap[n] = res
}
func main() {
//开启多个协程完成这个任务[200个协程]
for i :=1;i<=200;i++{
go test(i)
}
//遍历结果
time.Sleep(time.Second*10)
for i,v :=range myMap{
fmt.Println(i,v)
}//存在资源竞争关系,在编译的时候可以使用-race参数
使用互斥锁解决竞争关系
var (
myMap = make(map[int]int,10)
//lock全局互斥锁
//sync同步
//MUTE互斥
lock sync.Mutex
)
func test(n int){
res := 1
for i:=1;i<=n;i++{
res *= i
}
//将res放入到map中
//加锁
lock.Lock()
myMap[n] = res
//解锁
lock.Unlock()
}
也可以使用管道
管道Channel
解决协程资源竞争关系
- FIFO
- 线程安全
- channel由类型,一个string的channel只能放string类型数据
package main
import "fmt"
func main() {
//创建一个存放3个int类型的管道
var intChan chan int
intChan = make(chan int,3)
fmt.Println(&intChan)
//写数据
intChan <- 10
num := 101
intChan <- num
//管道的长度和cap
fmt.Println(len(intChan),cap(intChan))
//取数据
var num2 int
num2 =<-intChan
fmt.Println(num2,len(intChan),cap(intChan))
//在没有使用协程的情况下,把管道数据全部取出,会报错
}
任意类型数据管道
package main
import "fmt"
type Cat struct {
Name string
Age int
}
func main() {
//定义一个存放任意类型数据的管道
allChan := make(chan interface{}, 3)
allChan <- 10
allChan <- "tom"
cat := Cat{Name: "小花猫", Age: 3}
allChan <- cat
//得到管道里面的第三个元素,现推出前两个
<-allChan
<-allChan
newCat := <-allChan
fmt.Println(newCat)
//取出小花猫的名字,使用类型断言
a := newCat.(Cat)
fmt.Println(a.Name)
}
管道的关闭和遍历
关闭:关闭channel就不能往里面写出数据,但是可以读数据
func main() {
intChan := make(chan int,3)
intChan<- 1
intChan<- 2
close(intChan)//关闭管道
n1 := <-intChan
fmt.Println(n1)
}
遍历: 遍历的时候,如果管道没有不安比们就会垂涎deadlock的错误,如果已关闭,会正常遍历数据
intChan2 :=make(chan int,100)
for i:=0;i<100;i++{
intChan2 <- i
}
close(intChan2)
//遍历
for v:=range intChan2{
fmt.Println(v)
}
注意事项
-
管道可以声明只读或只写,默认管道是双向的
func main() { var chan1 chan<- int //只写 chan1 = make(chan int, 3) chan1 <- 20 var chan2 <-chan int num2 := <-chan2 fmt .Println(num2) }
-
使用select可以解决从管道取数据的阻塞问题
func main() { // 使用select可以解决管道取数据的阻塞问题 //定义一个管道,10个数据int intChan := make(chan int, 10) for i := 0; i < 10; i++ { intChan <- i } stringChan := make(chan string, 5) for i := 0; i < 5; i++ { stringChan <- "hello" + fmt.Sprintf("%d", i) } //传统的方法在遍历管道时,不关闭会阻塞deadlock //在实际开发中,可能不好确定什么时候关闭管道 //select可以解决 label: for { select { case v := <-intChan: // 这里intChan一直没有关闭,也不会一直阻塞,会自动到下一个管道case匹配 fmt.Printf("从intChan读取数据%d\n", v) case v := <-stringChan: fmt.Printf("从stringChan读取数据%s\n", v) default: fmt.Printf("都取不到,可以加自己的逻辑\n") //return break label } } }
-
goroutine中使用recover,解决协程中出现的painc,会导致程序崩溃
package main import ( "fmt" "time" ) func sayHello() { for i := 0; i < 10; i++ { time.Sleep(time.Second) fmt.Println("hello world") } } func test() { //使用defer + recover defer func() { //捕获test的painc if err := recover(); err != nil { fmt.Println("test(),发生错误", err) } }() var myMap map[int]string myMap[0] = "goland" } func main() { go sayHello() go test() for i := 0; i < 10; i++ { time.Sleep(time.Second) fmt.Println("main") } }
综合案例
协程和管道协调工作
-
开启一个writedata协程,向channel intChan中写入50个整数
-
开启一个readdata协程,从管道intChan中读取writedata写入的数据
-
writedata和readdata操作的是同一个管道
package main
import (
"fmt"
)
//writeData
func writeData(intChan chan int) {
for i := 1; i <= 50; i++ {
intChan <- i
//time.Sleep(time.Second)
}
close(intChan)
}
//readData
func readData(intChan chan int, exitChan chan bool) {
for {
v, ok := <-intChan
if !ok {
break
}
//time.Sleep(time.Second)
fmt.Println(v)
}
//readData完成任务
exitChan <- true
close(exitChan)
}
func main() {
//创建两个管道
intChan := make(chan int, 50)
exitChan := make(chan bool, 1)
go writeData(intChan)
go readData(intChan, exitChan)
for {
_, ok := <-exitChan
if !ok{
break
} //不停的读管道
}
}
如果把readData注销就会出现阻塞
求素数
package main
import "fmt"
//向intChan放入8000个数
func putNum(intChan chan int) {
for i := 1; i <= 8000; i++ {
intChan <- i
}
close(intChan)
}
//intChan取出数据,判断是否为素数,是就放到primeChan
func primeNum(intChan chan int, primeChan chan int, exitChan chan bool) {
var flag bool
for {
num, ok := <-intChan
//判断是否为素数
if !ok {
break //intChan取不到
}
for i := 2; i < num; i++ {
flag = true
if num%i == 0 {
flag = false
break
}
}
if flag {
//将这个数放到primeChan
primeChan <- num
}
}
fmt.Println("有一个primeNum取不到退出")
exitChan <- true
}
func main() {
intChan := make(chan int, 1000)
primeChan := make(chan int, 2000) // 放结果
//标识退出的管道
exitChan := make(chan bool, 4)
//开启一个协程,向intChan放入8000个数
go putNum(intChan)
//开启4个协程,从intChan取出数据,判断是否为素数,是就放到primeChan
for i := 0; i < 4; i++ {
go primeNum(intChan,primeChan,exitChan)
}
go func() {
for i := 0; i < 4; i++ {
<-exitChan
}
//当我们从exitChan取到4个结果,可以关闭管道
close(primeChan)
}()
//遍历primeNum,取出结果
for {
res, ok := <-primeChan
if !ok{
break
}
fmt.Println(res)
}
}