8.14GO之条件语句

8.14GO之条件语句

Go语言条件语句

一些大题的和类C语言无异,但是:

  • Go 没有三目运算符,所以不支持 ?: 形式的条件判断。

相当于Java中的:

    public boolean isEmpty(){
       return size == 0 ? true : false;
  }

Go之条件语句

语句描述
[if 语句] if 语句 由一个布尔表达式后紧跟一个或多个语句组成。
[if...else 语句] if 语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。
[if 嵌套语句] 你可以在 ifelse if 语句中嵌入一个或多个 ifelse if 语句。
[switch 语句] switch 语句用于基于不同条件执行不同动作。
[select 语句] select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。
Go条件语句之select语句

声明格式:

select {
   case communication clause :
      statement(s);      
   case communication clause :
      statement(s);
   /* 你可以定义任意数量的 case */
   default : /* 可选 */
      statement(s);
}

简单理解:

  • chan关键字定义了goroutine中的管道通信,一个goroutine可以和另一个goroutine进行通信。

什么是goroutine

goroutine是Go中最基本的执行单元。

特点:

  • 每一个Go程序至少有一个Goroutine:主Goroutine。

线程(Thread)

特点:

  • 轻量级进程(Lightweight Process,LWP)

  • 程序执行流的最小单元

一个标准线程的组成:

  • 线程ID

  • 当前指令指针(PC)

  • 寄存器集合

一个线程的特点:

  • 是进程中的一个实体,是被系统独立调度和分派的基本单位

  • 多线程之间 共享堆,不共享栈。 线程切换由操作系统调度

协程(coroutine)

特点:

  • 称微线程与子例程(或者称为函数)

  • 是一种程序组件。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛

一个协程的特点:

  • 多协程之间 共享堆,不共享栈

线程与协程的不同点
  • 线程切换由操作系统调度

  • 协程的切换由程序员在代码中显示控制

好处:

  • 避免了上下文切换的额外耗费

  • 兼顾了多线程的优点

  • 简化了高并发程序的复杂

Goroutine--->需要深入了解、实践。目前还不是很明白。

特点:

  • 协程不是并发的,而Goroutine支持并发的。

  • Goroutine可以理解为一种Go语言的协程。

  • 同时它可以运行在一个或多个线程上。-

  • goroutine支持协程之间的互相通信--->使用关键字chanchannel的简写

channel的特点

特点:

  • 一个 channels 是一个通信机制,可以让一个 goroutine 通过它给另一个 goroutine 发送值信息。

  • 每个 channel 都有一个特殊的类型,也就是 channels 可发送数据的类型。一个可以发送 int 类型数据的 channel 一般写为 chan int。

和多线程ThreadLocal不一样的地方:

ThreadLocal:

  • ThreadLocal能够放一个线程级别的变量

  • 本身能够被多个线程共享使用,又能达到线程安全的目的

  • 在多线程环境下保证成员变量的安全

ThreadLocal相当于一个共享的内存区域,供多个线程共享堆的数据。线程只能共享堆的数据,不能共享栈的数据。

Goroutine:

  • Go语言提倡使用通信的方法代替共享内存

  • 资源需要在 goroutine 之间共享时,通道在 goroutine 之间架起了一个管道,并提供了确保同步交换数据的机制。

  • 因为go是强类型语言,所以声明通道时,需要指定将要被共享的数据的类型。

  • 可以通过通道共享goroutine当中的内置类型、命名类型、结构类型和引用类型的值或者指针。

可以说goroutine实现了协程之间的栈的数据的共享。

通信的方法就是使用通道(channel),如下图所示:

channel 是一种队列一样的结构。

channel规则的特点:

  • 可以向一个goroutine建立多个channel通道

  • 同时只能有一个 goroutine 访问通道进行发送和获取数据。

  • goroutine遵循先入先出(First In First Out)的规则,保证收发数据的顺序。--->和栈空间的规则一样

通道的使用方式
  • 声明通道

  • 创建通道

  • 使用通道发送数据


通道本身需要一个类型进行修饰,就像切片类型需要标识元素类型。通道的元素类型就是在其内部传输的数据类型

声明通道类型格式:
var 通道变量 chan 通道类型
  • 通道类型:通道内的数据类型。

  • 通道变量:保存通道的变量。--->通道本身也是一个变量,

chan 类型的空值是 nil,声明后需要配合 make 后才能使用。

创建通道格式--->通道本身是引用类型,需要使用make进行创建
通道实例 := make(chan 数据类型)
  • 数据类型:通道内传输的元素类型。

  • 通道实例:通过make创建的通道句柄。--->可以理解为在堆中开辟一个空间

示例:

package main

import (
"expvar"
"fmt"
)

func main() {
ch1 := make(chan int) //创建一个整数类型的通道
ch2 := make(chan interface{}) //创建一个空接口类型的通道, 可以存放任意格式

type Equip struct {
expvar.Var
}

ch2 := make(chan *Equip)
fmt.Println()
}
使用通道发送数据--->操作符:<-

通道发送苏剧的格式:

通道变量 <- 
  • 通道变量:通过make创建好的通道实例。--->这是一个堆里面的实际对象,对应到一个堆的地址

  • 值:可以是变量、常量、表达式或者函数返回值等。值的类型必须与ch通道的元素类型一致。

示例:

package main

func main() {
ch1 := make(chan int) //创建一个整数类型的通道
ch2 := make(chan interface{}) //创建一个空接口类型的通道, 可以存放任意格式

ch1 <- 1
ch2 <- 3.1415926
ch2 <- "Hello,World!"
}
使用通道接受数据--->操作符:<-

通道接收数据的特征:

  • 通道的收发操作在不同的两个 goroutine 间进行。

    • 通道的数据在没有接收方处理时,数据发送方会持续阻塞,因此通道的接收必定在另外一个 goroutine 中进行。

  • 接收将持续阻塞直到发送方发送数据。

    • 接收方接收时,通道中没有发送方发送数据,接收方也会发生阻塞,直到发送方发送数据为止。

  • 每次接收一个元素。

    • 通道一次只能接收一个数据元素。

概括:

  • 通过通道传送数据需要两个goroutine

  • 发送没有接收会阻塞goroutine,接收没有发送数据也会阻塞goroutine--->非常类似Java多线程中的线程通信模型

  • 可以创建非阻塞接收数据的类型

接收数据的四种写法:

  1. 阻塞式接收数据
  2. 非阻塞式接收数据
  3. 接收任意数据,忽略接收的数据
  4. 循环接收

阻塞式接收数据:

data <- ch

执行该语句时将会阻塞,直到接收到数据并赋值给 data 变量。

非阻塞式接收数据:

data, ok <- ch
  • data:表示接收到的数据。未接收到数据时,data 为通道类型的零值。

  • ok:表示是否接收到数据。

非阻塞的通道接收方法可能造成高的 CPU 占用,因此使用非常少。如果需要实现接收超时检测,可以配合 select 和计时器 channel 进行

接收任意数据,忽略接收的数据:

<- ch

阻塞接收数据后,忽略从通道返回的数据。执行该语句时将会发生阻塞,直到接收到数据,但接收到的数据会被忽略。

通过通道在 goroutine 间阻塞收发实现并发同步。

使用通道做同步并发的实例:

package main

import "fmt"

func main() {
//构建一个通道
ch := make(chan int)

//开启一个并发匿名函数
go func() {
fmt.Println("start goroutine")

//通过通道通知main的goroutine
ch <- 0
fmt.Println("exit goroutine")
}()

fmt.Println("wait goroutine")

//等待匿名goroutine
<- ch

fmt.Println("all done")
}

可以看到执行顺序:

  1. 先执行main中的构建和通道接收的内容。到<- ch此时goroutine进入阻塞状态

  2. 接着等到go func()当中的goroutine运行向通道ch中添加值

  3. 最后main中收到了值以后解除阻塞状态,往下运行

所以输出顺序是:

wait goroutine
start goroutine
exit goroutine
all done

代码说明:

ch := make(chan int) //构建一个同步用的通道。
go func() //开启一个匿名函数的并发。
ch <- 0 //匿名 goroutine 即将结束时,通过通道通知 main 的 goroutine,这一句会一直阻塞直到 main 的 goroutine 接收为止。
<- ch //开启 goroutine 后,马上通过通道等待匿名 goroutine 结束。

虽然是并发运行但是通过阻塞的方式还是可以实现数据的安全、准确性

循环接收:

借用 for range 语句进行多个元素的接收操作

for data := range ch {
}

特点:

  • 通道 ch 可以进行遍历--->遍历的结果就是接收到的数据。数据类型就是通道的数据类型。

  • 通过 for 遍历获得的变量只有一个,即 data。

有点类似增强for循环

示例:

package main

import (
"fmt"
"time"
)

func main() {
//构建一个通道
chNo2 := make(chan int)

//开启一个并发匿名函数
go func() {

//for循环,从3->0
for i := 3; i > 0; i-- {
//每次将循环的值发送到main的goroutine中
chNo2 <- i

//每次发送完时等待
time.Sleep(time.Second)
}
}()

//遍历接收通道的数据
for data := range chNo2 {
//打印通道数据
fmt.Println(data)

//当遇到数据0时,退出接收循环
if data == 0 {
break
}
}
}

代码说明:

ch := make(chan int)
//通过 make 生成一个整型元素的通道。
go func() //将匿名函数并发执行。
ch <- i //将 3 到 0 之间的数值依次发送到通道 ch 中。
for data := range ch //使用 for 从通道中接收数据。

本篇内容参考:

Go语言通道(chan)——goroutine之间通信的管道

Go goroutine理解 - SegmentFault 思否

go的并发实现原理在后续学习到以后会深入的记录学习过程。

原文地址:https://www.cnblogs.com/JunkingBoy/p/15147821.html