协程和管道

协程和管道

协程 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:协程

    image-20210203230018063

image-20210203230112902

设置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)

	}


}

posted @ 2021-02-06 23:15  小子,你摊上事了  阅读(58)  评论(0编辑  收藏  举报