[Golang]我的Go+语言初体验——协程篇
Posted Lazy@慵懒
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Golang]我的Go+语言初体验——协程篇相关的知识,希望对你有一定的参考价值。
前言
协程这个词,相信看到这篇文章的小伙伴并不陌生,我自己也是因为这个词,而接触Golang的。
那什么是协程? 协程和我们平时接触的进程和线程有什么关联和不同呢?那我们来一 一讨论一番。
进程,线程与协程
我们在文件中编写一段代码,这个文件称为源文件,将源文件经过一系列操作,会生成可执行的二进制文件,这个二进制文件我们称之为程序(实质是文件),那么其一定是存储于磁盘当中的。当我们使用./xxx命令的时候(也就是我们常说的启动程序),会出现程序对应的结果。而这个启动的程序就是我们所要说的进程(所谓的进程,就是启动的程序,会占用内存等系统资源),而进程当中又包含了线程。线程是CPU调度的基本单位,而进程是资源分配的基本单位。
eg:程序我们可以认为是一个剧本,拿着剧本可以去各地舞台演出,通过程序我们可以启动多个进程(进程的名称可能不同,但是功能相同,我们可以启动多个QQ飞车,都又一摸一样的游戏模式),而线程更像是演员,完成进程中的一部分功能(eg:在飞车中,我们可以一边跑图,一边听歌)
而协程是微线程,就如线程被称为轻量级进程一般,协程比线程更加轻量级,一个线程中可以有多个协程。
区别:
进程和线程是操作系统进行管理的,而协程是编译器级的,不受操作系统内核控制,直接由程序控制,避免了线程切换带来的开销,轻量级这是协程的巨大魅力所在,可以轻松的创建数万个协程,不必担心系统资源枯竭问题。
Golang中的协程
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协程执行完毕退出之后,其上的协程将会全部退出。
通道channel
通过上面的程序,我们可以观察到,一旦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
以上是关于[Golang]我的Go+语言初体验——协程篇的主要内容,如果未能解决你的问题,请参考以下文章