并发
在Go语言中较为出名的就是他本身支持高并发机制,通过在程序中使用关键字go 函数名() 来创建一个并发任务单元,然后系统将任务单元放置在系统队列中,等待调度器安排合适系统线程去获取执行权并执行任务单元(就是函数)。在这其中每个任务单元保存了该函数的指针、传入的参数、执行所需的栈内空间大小(2KB, 正是由于该原因所以才能创建成千上万的并发任务单元)。
例子:
package main import ( "fmt" "time" ) func PrintTest(s string){ for i:= 0; i<5; i++ { time.Sleep(100*time.Millisecond) # 因为进程不会等待并发任务执行结束,所以设置一个睡眠时间来确保并发任务的执行。 fmt.Println(s) } } func main(){ go PrintTest("hello") PrintTest("word") }
word // 可以看到word输出了5次,hello只输出了4次。
hello
hello
word
word
hello
hello
word
word
为了避免这个情况我们可以使用channel进行阻塞解决这个问题。
func PrintTest(s string){
for i:= 0; i<5; i++ {
fmt.Println(s)
}
}
func main(){
c := make(chan int)
go func(s string, c chan int){
for i:= 0; i<5; i++ {
fmt.Println(s)
}
close(c)
}("hello", c)
PrintTest("word")
<-c // 等待结束
}
当然如果是多个任务执行,还是建议使用sysc.WaitGroup来设置定计数器,统计并发单元的数量。直到为0时才退出程序。(这里注意一下就是虽然WaitGroup.Add()实现了原子操作,但是还是建议在goroutine外部实现累加计数器,避免在执行Add()的时候,主程序已经执行到wait部分,并判断为空退出执行了。)
package main import ( "fmt" "sync" ) func main(){ var wg sync.WaitGroup for i:= 0; i < 5; i++ { wg.Add(1) go func(s int) { for i := 0; i < 5; i++ { fmt.Println(s) } wg.Done() # 计数减去1 }(i) } wg.Wait() # 等待结束 }
当然我们也可以使用多个wait()来控制任务的执行流程。
package main
import (
"fmt"
"sync"
"time"
)
func main(){
var wg sync.WaitGroup
wg.Add(1)
go func(){
wg.Wait()
fmt.Println("func a wait")
}()
go func() {
time.Sleep(time.Second)
fmt.Println("func b wait")
for i := 0; i < 5; i++ {
fmt.Println(i)
}
wg.Done()
}()
wg.Wait()
}
func b run // 运行结果
0
1
2
3
4
func a wait
通道
在底层实现来说,通道只是一个队列。同步模式下,发送方和接收方双方匹配,然后直接复制数据给对方。如果配对失败,则进行等待,直到配对成功后才被唤醒。而异步模式下,需要通道设置缓冲区大小,这样发送方将数据写入通道之后并不需要接受方马上进行接收,而是在通道写满之后,如果接受方还未读取数据就会导致发送方阻塞,直到接受方将缓冲数据进行读取。
未设置缓冲区的通道
package main import "fmt" func sum(s []int, c chan int){ sum_ := 0 for _, i :=range(s){ sum_ += i } c<-sum_ } func main(){ s := []int{1,2,3,4,5,6} c := make(chan int) go sum(s[:len(s)/2], c) go sum(s[len(s)/2:], c) x, y := <-c, <-c fmt.Println(x,y) // 输出 15 6 }
设置带有缓冲区的通道,我们可以一直添加数组(直到小于数组)
package main
import (
"fmt"
)
func sum(s []int, c chan int){
sum_ := 0
for _, i :=range(s){
sum_ += i
c<-sum_
}
close(c)
}
func main(){
s := []int{1,2,3,4,5,6}
c := make(chan int, 6)
go sum(s, c)
for i := range c{
fmt.Println(i)
}
}
当然除了传递数据之外,通道还可以用来进行时间通知。
package main import ( "fmt" ) func main(){ s := make(chan struct{}) # 结束事件管道 c := make(chan string) # 数据管道 go func() { t:= <- c fmt.Println(t) close(s) }() c <-"hi" # 发送消息 <-s # 等待结束 }
例子2
package main import "fmt" func main(){ s := make(chan struct{}) c := make(chan int) go func() { defer close(s) for { x, err := <-c # 判断通道是否已经关闭 if !err{ return } fmt.Println(x) } }() c <- 1 c <- 2 c <- 3 close(c) <- s # 等待通道关闭 }
创建通道的时候,默认是双向的。但是有时候我们也可以使用单向通道来管理程序逻辑的执行顺序,(如果违背了单项通道的发送方向,则会无效操作, 另外还有就是在接收端不能对通道进行关闭,无效操作)
package main import ( "fmt" "sync" ) func main(){ var wg sync.WaitGroup wg.Add(2) c := make(chan int) var send chan<- int = c // 定义单向发送通道,无法将其转换回双向通道 var recv <-chan int = c // 定义单项接收通道, 无法将其转换双向通道 go func() { defer wg.Done() for x:= range recv{ fmt.Println(x) } }() go func() { defer wg.Done() defer close(c) for i := 0; i<3; i++{ send <- i } }() wg.Wait() }
在处理多个通道的时候,我们可以使用select语句,她会随机选择一个可用通道做收发操作, 当然即使case中都是同一个通道,select在选择的时候也会随机选择。
package main import ( "fmt" "sync" ) func main(){ var wg sync.WaitGroup wg.Add(2) c, b := make(chan int), make(chan int) // 创建两个通道 go func() { // 接收函数 defer wg.Done() var ( name string x int ok bool ) for { select { // 随机选择一个通道进行接收数据 case x, ok = <-c: name = "a" case x, ok = <-b: name = "b" } if !ok { return } fmt.Println(name, x) } }() go func() { defer wg.Done() defer close(c) defer close(b) for i := 0; i<6; i++{ // 随机选择一个通道进行发送 select { case c <- i: case b <- i*10: } } }() wg.Wait() }
a 0 // 输出结果
b 10
a 2
b 30
b 40
a 5
待续