go select 与 for 区别_go语言学习笔记(29)goroutine和channel

goroutine基本介绍

假如我们要计算 1-200000里哪些是素数?

1:我们传统方法就是使用一个循环,循环判断各个数是不是素数。

2:使用并发或者并行的方式,将统计素数的任务分配给多个goroutine去完成。

进程和线程说明

1:进程就是程序在操作系统中的一个执行过程,是系统进行资源分配和调度的基本单位。

2:线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位。

3:一个进程可以创建和销毁多个线程,同一个进程中的多个线程可以并发执行。

4:一个程序至少有一个进程,一个进程至少有一个线程。

并发和并行

1:多线程程序在单核上运行,就是并发

2:多线程程序在多核上运行,就是并行

go协程和go主线程

1:go主线程(有程序员直接称为线程/也可以理解成进程):一个go线程上,可以起多个协程,你可以理解成:协程是轻量级线程。【编译器做优化】

2:go协程的特点

(1)有独立的栈空间

(2)共享程序堆空间

(3)调度由用户控制

(4)协程是轻量级线程

goroutine案例演示

编写一个程序,完成如下功能:

1:在主线程中,开启一个goroutine,该协程每隔1秒输出“hello”

2:在主线程中也每隔一秒输出“go”,输出10次后,退出程序

3:要求主线程和goroutine同时运行。

package main
import ( 
	"fmt"
	"time"
)
func test(){
	for i:=1; i <= 10; i++{
		fmt.Println("hello",i)
		time.Sleep(time.Second)
	}
}
func main()  {
  go test()
   for i:=1; i <= 10; i++{
	fmt.Println("   go",i)
	time.Sleep(time.Second)
}
	
	}

ee1d7392a6f6d502e8a5aad582aac2f3.png

小结:

1:主线程是一个物理线程,直接作用在cpu上的。是重量级的,非常耗费cpu资源。

2:协程从主线程开启的,是轻量级的线程,是逻辑态,对资源消耗相对小。

3:go语言的协程机制是重要的特点,可以轻松的开启上万个协程。其他编程语言的并发机制是一般基于线程的,开启过多的线程,资源耗费大,这里就突显go语言的并发上的优势。

goroutine的调度模型

MPG模式基本介绍

M:操作系统的主线程 (是物理线程)

P:协程执行需要的上下文,可以看做一个局部调度器,使go代码在一个线程上跑,它是实现从N:1到N:M映射的关键。( 就是需要的资源和当时运行的状态)

G:协程,代表一个goroutine,它有自己的栈,instruction pointer和其它信息(正在等待的channel等等),用于调度

设置go语言运行的cpu数

package main
import ( 
	"fmt"
   "runtime"
)
func main()  {
 //获取当前电脑运行的cpu数
  num := runtime.NumCPU()
  fmt.Println("CPUnum=",num)
  //可以设置主键使用多少个CPU,这里我保留一个
  runtime.GOMAXPROCS(num - 1)
  fmt.Println("ok")
	}

52a760f455266da11e5389e937c6fd65.png

go1.8后,默认让程序运行在多个核上,可以不用设置

go1.8前,需要设置一下,可以高效的利用cpu

channel(管道)

1:channel本质就是一个数据结构-队列

2:数据先进先出

3:线程安全,多goroutine访问时,不需要加锁,就是说channel本身就是线程安全的

4:channel是有类型的,一个string的channel只能存放string类型数据。

定义/声明

var 变量名 chan 数据类型

举例:

var intChan chan int (用于存放int数据)

var mapChan chan map[int]string(用于存放map[int]string数据)

car perChan chan Person

。。。

说明:

1:channel是引用类型

2:channel必须初始化才能写入数据,即make后才能使用

3:管道是有类型的额,如intChan只能写入整数int类型

初始化:

intChan = make(chan int,3)

channel可以声明为只读,或者只写的性质

//声明只写
var chan2 chan<- int
chan2 = make(chan int,3)
chan2<- 33
//声明只读
var chan3 <-chan int
num := <-chan3

向管道写入数据:

intChan <- 10

从管道读取数据:

num:= <- intChan

channel使用细节说明:

1:channel中只能存放指定的数据类型

2:channel的数据放满后,就不能再放入了

3:如果从channel取出数据后,可以继续放入

4:在没有使用协程的情况下,如果channel数据取完了,再取,就会报dead lock

channel的关闭

使用内置函数close可以关闭channel,当channel关闭后,就不能再向channel写数据了,但是仍然可以从该channel读取数据。

channel的遍历

channel支持for-range的方式进行遍历。

注意:在遍历时,如果channel没有关闭,则会出现dead lock的报错

//在遍历时,要先关闭channel
clase(intChan)
//遍历管道是不能使用普通的for循环
//这里的for-range没有i这个输出下标,所以直接 如下就可以遍历
for v:= range intChan {
fmt.Println("v=",v)
}

goroutine和channel结合的应用案例

1:开启两个协程,一个协程向管道写入50个整数,一个协程从管道读取数据。

package main
import ( 
	"fmt"
	 "time"
//    "runtime"
)
var (
	myMap = make(map[int]int,10)
)
func writeData(intChan chan int){
	for i:=1; i<=50; i++{
		intChan <- i
		fmt.Printf("写入数据=%vn",i)
//加个时间休眠来看出这两个协程是同步的
		time.Sleep(time.Second)
	}
   close(intChan)
}
func readData(intChan chan int,boolChan chan bool){
for{
	v,ok := <- intChan
	if !ok{
		break
	}
	fmt.Printf("读出数据为=%vn",v)
	time.Sleep(time.Second)
}
 boolChan <- true
 close(boolChan)

}

func main()  {
var intChan chan int
intChan = make(chan int,50)
boolChan := make(chan bool,1)
go writeData(intChan)
go readData(intChan,boolChan)
for {
   _,ok := <-boolChan
   if !ok{
break
   }
}
	}

fecf06aeacd51a80ef0014082af9740d.png

阻塞说明:

如果,在编译器在运行时,发现一个管道只有写,没有读,则该管道就会阻塞。

应用案例2: 要求统计1-6666的数字中,哪些数是素数,使用并发/并行的方式,将统计素数的任务分配给4个goroutine去完成。

package main
import ( 
	"fmt"
	 "time"
//    "runtime"
)
func writeData(intChan chan int){
	for i:=1; i<=6666; i++{
		intChan <- i
	}
   close(intChan)
}
//从intchan取出数据并判断是否为素数,如果是就放入primeChan
func primeNum(intChan chan int,primeChan chan int,boolChan chan bool){
	
	var flag bool
	for{
		time.Sleep(time.Millisecond)
		num,ok := <- intChan
		if !ok{  //intchan里面娶不到东西了
			break
		}
		flag = true
		for i := 2; i< num; i++{
			if num % i == 0{
				flag = false
				break
			}
		}
		if flag{
			primeChan<- num
		}
	
	}
	 boolChan <- true
	
	}
func readData(primeChan chan int){
for{
	v,ok := <- primeChan
	if !ok{
		break
	}
	fmt.Printf("素数为=%vn",v)
}

}

func main()  {
var intChan chan int
intChan = make(chan int,1000)
primeChan := make(chan int,6666) //放入素数结果
boolChan := make(chan bool,4)
//开启一个协程,向intchan放入1-6666个数
go writeData(intChan)
//开启4个协程,从intChan取出数据并判断是否为素数
for i:=1; i <= 4; i++{
go primeNum(intChan,primeChan,boolChan)
}
go func(){
for {
   if len(boolChan) == 4{
	   close(primeChan)
break
   }
}
}()
readData(primeChan)
	}

f0823267fbaa02e4e475567ac60ad8ff.png

用select解决管道阻塞问题案例

代码:

package main
import ( 
	"fmt"
	 _"time"
//    "runtime"
)


func main()  {
var intChan chan int
intChan = make(chan int,10)
for i := 1; i<=10;i++{
	intChan<- i
}
stringChan := make(chan string,4)
for i := 1; i<=4; i++{
	stringChan<- fmt.Sprintf("lalala  %d",i)
}
//传统方法在遍历管道的时候,如果不关闭会阻塞
//所以在实际开发中,我们可能会遇到一种情况:我们不好确定什么时候关
//可以使用select方式解决
for{
	select{
	case v := <- intChan :
		fmt.Printf("intChan数据有 %dn",v)
	case v:= <- stringChan :
	fmt.Printf("stringChan数据为 %sn",v)
	default :
	fmt.Println("nothing ,over ")
	return
}
}
	}

a07866184745526bd9a9500da971ecd4.png

goroutine错误机制处理

我们在goroutine中使用recover,解决协程中出现panic,导致程序崩溃停止的问题

package main
import ( 
	"fmt"
	 "time"
//    "runtime"
)
func say(){
	for i := 1; i<=10;i++{
		time.Sleep(time.Second)
		fmt.Println("go",i)
	}
}
func test(){
   //这里defer函数来处理错误
	defer func(){
//使用recover来捕获panic进行处理,这样协程发生问题不会影响其他线程
		if err := recover(); err != nil{
			fmt.Println("test 错误=",err)
		} 
	}()
	var myMap map[int]int
	myMap[0] = 100       //error 没有make
}
func main()  {
	go say()
	go test()
	for i := 1; i<=10;i++{
		time.Sleep(time.Second)
		fmt.Println("main ",i)
	}
}

3ed42879977332d855573ef53b34b5ec.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值