8.14GO之条件语句
Go语言条件语句
一些大题的和类C语言无异,但是:
-
Go 没有三目运算符,所以不支持 ?: 形式的条件判断。
相当于Java中的:
public boolean isEmpty(){
return size == 0 ? true : false;
}
Go之条件语句
语句 | 描述 |
---|---|
[if 语句] |
if 语句 由一个布尔表达式后紧跟一个或多个语句组成。 |
[if...else 语句] |
if 语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。 |
[if 嵌套语句] |
你可以在 if 或 else if 语句中嵌入一个或多个 if 或 else if 语句。 |
[switch 语句] |
switch 语句用于基于不同条件执行不同动作。 |
[select 语句] |
select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。 |
Go条件语句之select
语句
简单理解:
-
chan关键字定义了goroutine中的管道通信,一个goroutine可以和另一个goroutine进行通信。
什么是goroutine
goroutine是Go中最基本的执行单元。
特点:
-
每一个Go程序至少有一个Goroutine:主Goroutine。
线程(Thread)
特点:
-
轻量级进程(Lightweight Process,LWP)
-
程序执行流的最小单元
一个标准线程的组成:
-
线程ID
-
当前指令指针(PC)
-
寄存器集合
-
堆
-
栈
一个线程的特点:
-
是进程中的一个实体,是被系统独立调度和分派的基本单位
-
多线程之间 共享堆,不共享栈。 线程切换由操作系统调度
协程(coroutine)
特点:
-
称微线程与子例程(或者称为函数)
-
是一种程序组件。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛
一个协程的特点:
-
多协程之间 共享堆,不共享栈
线程与协程的不同点
-
线程切换由操作系统调度
-
协程的切换由程序员在代码中显示控制
好处:
-
避免了上下文切换的额外耗费
-
兼顾了多线程的优点
-
简化了高并发程序的复杂
Goroutine--->需要深入了解、实践。目前还不是很明白。
特点:
-
协程不是并发的,而Goroutine支持并发的。
-
Goroutine可以理解为一种Go语言的协程。
-
同时它可以运行在一个或多个线程上。-
-
goroutine支持协程之间的互相通信--->使用关键字
chan
是channel
的简写
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多线程中的线程通信模型
-
可以创建非阻塞接收数据的类型
接收数据的四种写法:
- 阻塞式接收数据
- 非阻塞式接收数据
- 接收任意数据,忽略接收的数据
- 循环接收
阻塞式接收数据:
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")
}
可以看到执行顺序:
-
先执行main中的构建和通道接收的内容。到
<- ch
此时goroutine进入阻塞状态 -
接着等到
go func()
当中的goroutine运行向通道ch
中添加值 -
最后
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的并发实现原理在后续学习到以后会深入的记录学习过程。