除了基础语法,Go 语言还具有一些常用的具有优势的语言特性,例如goroutine、channel。
goroutine
轻量级线程(协程,栈KB级别)——goroutine,可以便捷地实现并发,创建和销毁的开销很小。线程的栈MB级别。协程的使用:go + 函数
。由于goroutine的设计,它们可以高效地在少量的线程上运行(可以在单个Go程序中创建成千上万个goroutine)并且这些goroutine可以并发执行。
Goroutine的调度是由Go语言的运行时系统自动进行的,它会在多个可用的线程上动态地调度和管理goroutine的执行。这种调度方式称为“工作窃取调度”,其特点是将任务均匀分配给可用的线程,以实现最佳的性能和资源利用。它极大地简化并发编程的复杂性,合理地利用goroutine和通道,可以实现高效的并发、并行计算、网络编程、IO操作等。
需要注意的是,虽然goroutine非常轻量,但在编写代码时也需要遵守一些规范,例如避免共享数据的竞态条件、正确处理通道的关闭等,以确保并发程序的正确性和稳定性。
下面是一个用函数快速打印的例子:
func main() {
for i:=0;i<5;i++{
go func(j int){
hello(j)
} (i)
}
//用 go 关键字启动了5个hello函数的协程,输出是乱序的(各协程并行)
time.Sleep(time.Second) //让程序睡眠,以便观察输出
}
func hello(i int) { //hello函数有整型参数,打印hello:i
println("hello:"+fmt.Sprint(i))
}
channel
goroutine 之间的通信可以使用 channel 来实现,通过通信而共享内存。(不是通过共享临界区内存而通信,影响程序性能)channel 是一种引用类型(可以是任何合法的Go语言类型,如整数、字符串、结构体等),可以保证顺序性(即并发安全)。这是Go语言中用于协调不同goroutine之间交换数据的重要机制。通过channel,可以实现多个goroutine之间的安全数据传递和共享。make(chan 元素类型,[缓冲大小])
,无缓冲通道make(chan int)
将导致发射和接收同步化;有缓冲通道make(chan int,2)
,使得通道能够存储多个值,而不是仅能存储一个值,从而减少发送和接收操作之间的阻塞次数,提高并发性能,符合生产消费模型的执行效率(消费速度不拖慢生产速度)。
当在goroutine中发送或接收数据时,如果没有可用的数据或接收方没有准备好,发送和接收操作都会阻塞当前的goroutine,直到满足条件后才会继续执行。
下面是一个例子:
func main() {
src := make(chan int)
dest := make(chan int,3)
go func(){ //子协程A发送0~9给B
defer close(src)
for i:=0;i<10;i++{
src <- i //通过src实现AB两个子协程之间的通信
}
}()
go func(){ //子协程B发送平方数给主协程
defer close(dest)
for i:= range src{ //通过src实现AB两个子协程之间的通信
dest <- i*i
}
}()
for i:= range dest{ //主协程输出接收到的的平方数
println(i) //也可是其它复杂操作
}
}
func sum(x, y int, ch chan int) {
//sum 函数有两个整型参数,将两数之和发送到一个 channel 中
time.Sleep(time.Millisecond * 500)
ch <- x + y
}
Channel在Go语言中还有一些其他的特性和用法。通过灵活运用这些特性,我们可以更好地利用Channel来实现并发编程中的各种需求和模式。
- 除了普通的发送和接收操作外,还可以使用特殊的语法形式来表示对channel的关闭和判断。关闭通道后,仍然可以从通道中接收已经发送的数据,但不能再向通道中发送新的数据。如下面的例子(如果通道已关闭且没有数据可接收,则ok为false):
close(ch) // 关闭通道ch,表示不再向通道发送新的数据
value, ok := <- ch // 判断通道ch是否关闭,并将接收到的值赋给value
- 单向通道(Unidirectional Channels):可以将通道限制为只能发送或只能接收。这样可以增加程序的安全性和表达性。例如:
ch := make(chan int)
sendOnlyCh := make(chan<- int) // 只能发送的通道
receiveOnlyCh := make(<-chan int) // 只能接收的通道
// 可以将通常的通道类型转换为单向通道类型
sendOnlyCh = ch
receiveOnlyCh = ch
- 选择(Select) 语句:可以通过select语句同时监听多个通道的操作,从而实现非阻塞的 多路复用。它类似于switch语句,用于处理通道操作的选择问题。例如:
select {
case <-ch1:
// 处理ch1通道的接收操作
case data := <-ch2:
// 处理ch2通道的接收操作,并将接收到的数据赋值给data变量
case ch3 <- value:
// 将value发送到ch3通道
default:
// 如果所有通道都没有准备好,则执行默认操作
}
- 遍历通道(Range Over Channels):可以使用for...range语句遍历通道中的值,直到通道被关闭。这种方式非常适用于处理生产者-消费者模式的场景。例如:
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i // 发送数据到通道
}
close(ch) // 关闭通道
}()
for value := range ch {
// 处理从通道接收到的值
fmt.Println(value)
}
个人感想:通过学习goroutine和channel的原理以及用法,更加深刻地理解了GO语言高并发的特性。学习了理论知识,还需要动手实践!