- 前言
- Golang中的协程
- 通道channel
- 创建通道:
- 使用通道发送数据和接收数据
- 关闭通道
协程这个词,相信看到这篇文章的小伙伴并不陌生,我自己也是因为这个词,而接触Golang的。
那什么是协程? 协程和我们平时接触的进程和线程有什么关联和不同呢?那我们来一 一讨论一番。
进程,线程与协程
我们在文件中编写一段代码,这个文件称为源文件,将源文件经过一系列 *** 作,会生成可执行的二进制文件,这个二进制文件我们称之为程序(实质是文件),那么其一定是存储于磁盘当中的。当我们使用./xxx命令的时候(也就是我们常说的启动程序),会出现程序对应的结果。而这个启动的程序就是我们所要说的进程(所谓的进程,就是启动的程序,会占用内存等系统资源),而进程当中又包含了线程。线程是CPU调度的基本单位,而进程是资源分配的基本单位。
eg:程序我们可以认为是一个剧本,拿着剧本可以去各地舞台演出,通过程序我们可以启动多个进程(进程的名称可能不同,但是功能相同,我们可以启动多个QQ飞车,都又一摸一样的游戏模式),而线程更像是演员,完成进程中的一部分功能(eg:在飞车中,我们可以一边跑图,一边听歌)
而协程是微线程,就如线程被称为轻量级进程一般,协程比线程更加轻量级,一个线程中可以有多个协程。
区别:
进程和线程是 *** 作系统进行管理的,而协程是编译器级的,不受 *** 作系统内核控制,直接由程序控制,避免了线程切换带来的开销,轻量级这是协程的巨大魅力所在,可以轻松的创建数万个协程,不必担心系统资源枯竭问题。
Go语言中的协程叫作Goroutine。Goroutine由Go程序运行时(runtime)调度和管理.它也属于抢占式任务处理,和多线程任务处理类似。
在Golang中启动一个协程,非常的简单,只需要在函数或这方法前面添加关键字go。
注:
函数和方法一般不会写返回值,如果有返回值也会被忽略掉。
eg:编写一个简单的协程程序:启动两个协程A,B打印数字1~7
package main import ( "fmt" "time" ) func main() { go ADD("A") go ADD("B") time.Sleep(3 * time.Second) fmt.Println("main done") } func ADD( str string) { for i := 1; i <= 7; i++ { fmt.Println(str," say:",i) time.Sleep(time.Microsecond) } }
结果:
A say: 1 B say: 1 B say: 2 A say: 2 A say: 3 B say: 3 B say: 4 A say: 4 A say: 5 B say: 5 B say: 6 A say: 6 A say: 7 B say: 7 main done
通过结果,我们观察到两个协程是并行执行的,如果我们将ADD函数中time.Sleep(time.Microsecond)去除掉,会观察到两个协程是并发执行的,这是因为每协程执行时间很短,协程B执行前,协程A已经执行完毕了退出了。
如果我们将main中的time.Sleep(3 * time.Second)去除掉,观察结果,会发现结果不完整,因为执行main函数是也会启动一个协程,如果这个main协程执行完毕退出之后,其上的协程将会全部退出。
通过上面的程序,我们可以观察到,一旦main函数执行完毕退出之后,启动的所有协程也会全部退出(即使没有完成对应的工作)。当然我们可以采用上面的方法,设置睡眠时间,但是这个时间要设置多久呢?过短的话,会出现没有完成全部工作就退出的情况;多长的话,程序的效率就很低,所以时间的把控是非常困难的。我们需要能够进行协程间通信,而channel就可以用来解决这个问题,而且十分的高效.
Go中盛行一句话:
Do not communicate by sharing memory; instead, share memory by communicating.
“不要以共享内存的方式来通信,相反,要通过通信来共享内存。”
channel就是这句话的产物,我们可以使用它来发送和接收共享资源,达到协程间通信的目的,同时还可以保证协程间的同步。
创建通道:声明channel类型的变量 语法:
var [变量名称] chan [数据类型] eg: var chanInt chan int 定义了一个名为chanInt的管道,该管道内可存放int类型的数据
channel是引用类型,其默认值为nil,在定义channel变量之后,我们需要使用make创建之后,才可以进行使用。如果在使用make创建时,不指定通道的大小或者指定通道的大小为0时,则会创建无缓冲通道。指定大小时会创建有缓冲的通道.
语法如下:
通道变量 :=make(通道类型,通道大小) eg: ch :=make(chan int) //无缓冲的通道 ch :=make(chan int,2) //有缓冲的通道使用通道发送数据和接收数据
无论发送数据还是接收数据,我们都会使用<-进行操作
发送数据
ch :=make(chan int) ch<-7 eg:我们创建了一个存放int类型数据的通道,将数值7放入该通道内
接收数据
1.阻塞接收 c:= <-ch eg:使用上面的方式就可以从通道ch读取一个数据,存储于变量c中 执行上面的语句的时候,会陷入阻塞状态,直到接收并且其赋给c。 阻塞接收完整写法 c,flag :=<-ch eg: c表示接收的数据,倘若没有接收到数据,则为通道存储类型的默认值 flag为一个布尔值,接收到数据为true,为false时表示通道已经被关闭了 2.忽略接收数据 <-ch 会将接收的数据丢弃掉,执行此语句channel会阻塞,其目的不是为了接收数据 ,而是阻塞当前的Goroutine 3.循环接收数据 可以使用for循环来读取桶道中所有数据,循环接收数据需要配合关闭通道来使用 1)普通for循环 for{ data,flag :=<-ch //如果flag为false,则通道已经被关闭了,没有数据传递了,退出 if !flag{ break; } } 2)for-range 会自动判断通道是否关闭,若通道关闭,自动退出 for data := range ch{ fmt.Println(data) }关闭通道
可以使用内置函数close来关闭通道;当通道关闭之后,就不能往通道内写入数据;而可以从通道里面读取数据。
package main import "fmt" func main(){ ch:=make(chan int,5); ch<-5; ch<-3; close(ch); fmt.Println(<-ch); fmt.Println(<-ch); ch<-7; } //5 //3 //panic: send on closed channel
从结果当中,我们可以看出,当往已经关闭的通道当中写入数据的时候,会引发panic
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)