[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+语言初体验——协程篇的主要内容,如果未能解决你的问题,请参考以下文章

初探go-golang语言初体验

Go语言之gorountine与管道初体验

go语言初体验

每天一点GO语言——Linux环境下安装Go语言环境以及编写Go语言程序初体验

dubbo go 初体验

我的Go+语言初体验——语法验证/性能测试篇(直男程序员的真实体验)