无缓冲的channel
下面代码一定报错,因为没有接收的函数就一个劲儿往c channel中发送数据
package main
func main() {
c := make(chan int) // make的第二个参数我们不传或者填写0,都会创建一个无缓冲的channel,此时必须声明一个go携程接收数据,否则一定会报错
c <- 1
c <- 2
n := <-c
time.Sleep(time.Second)
}
// fatal error: all goroutines are asleep - deadlock!
一定要创建一个go携程先接收,然后才可以往无缓冲的channel里面发送数据
// 正确示范如下
package main
func main() {
c := make(chan int)
go func() {
for {
n := <-c
fmt.Println(n)
}
}()
c <- 1
c <- 2
time.Sleep(time.Second)
}
有缓冲的channel
package main
func main() {
c := make(chan int, 3) // 创建可以一个缓冲区可以容纳三个int型的channel
c <- 1
c <- 2
c <- 3
//c <- 4 // 如果这里塞第四个值,而缓冲区大小我们设置的3的话,这里会报错fatal error: all goroutines are asleep - deadlock!
fmt.Println(<-c)
fmt.Println(<-c)
fmt.Println(<-c)
//fmt.Println(<-c) // 如果这里接收的值超过了channel的值,这里也会报死锁的错误,所以一定要注意缓冲区多大,就只能接受多大的值
//fmt.Println(n)
time.Sleep(time.Second)
}
如何判断channel是否已经接收完毕
注意无论有缓冲还是无缓冲的channel,channel数据发送完毕以后,一定要close
掉开辟的channel
,否则在我们在循环的时候会进入死锁状态
- 通过
range
接收数据
package main
func main() {
c := make(chan int, 3)
c <- 1
c <- 2
c <- 3
close(c) // 此处如果不关闭会进入死锁状态,fatal error: all goroutines are asleep - deadlock!
for v := range c {
fmt.Println(v)
}
fmt.Println("数据已经接收完毕")
time.Sleep(time.Second)
}
- 通过
v, ok := <-c
接收数据
package main
func main() {
c := make(chan int, 3)
c <- 1
c <- 2
c <- 3
close(c) // 此处不close也会报错fatal error: all goroutines are asleep - deadlock!
for {
v, ok := <-c
if !ok {
break
}
fmt.Println(v)
}
time.Sleep(time.Second)
}
协程间通信
这里简要介绍一下几种协程间通讯的方式
等待协程结束
一般我们如果写开辟go协程的话,最好要在代码中time.Sleep(time.Second)
几秒,要不然main函数执行完毕的时候,go协程还没有机会执行,下面是几种等待协程结束的方案
- 利用 sync.WaitGroup 这个包来做
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func main() {
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
fmt.Println("current id is :", id)
wg.Done()
}(i)
}
wg.Wait()
}
- 利用channel等待
假设通过协程投递多个任务,想要等待所有任务执行结束以后让代码逻辑继续往下走,那么可以使用下面的方法,<-isComplete
这个方法会阻塞至所有任务完成然后继续向下顺序执行
package main
import (
"fmt"
"time"
)
func main() {
taskChannel := make(chan int, 10)
// 任务是否已经结束
isComplete := make(chan bool, 10)
for i := 0; i < 10; i++ {
go func(i int) {
taskChannel <- i
// TODO 假设做了很多事情
time.Sleep(time.Second * 1)
fmt.Println("当前任务id :", i, "已经完成掉了")
isComplete <- true
}(i)
}
for i := 0; i < 10; i++ {
<-isComplete
}
fmt.Println("当前任务全部结束掉了")
}
channel等待的几种情形
场景:比如我们现在要做十个前端任务和十个后端任务,我们有以下需求
- 我们希望前端任务顺序执行,后端任务顺序执行
package main
import "fmt"
type task struct {
data chan string
done chan bool
}
func (t task) Run() {
for v := range t.data {
fmt.Printf("current data = %s\n", v)
t.done <- true
}
}
func createTask() task {
task := task{
data: make(chan string, 1),
done: make(chan bool, 1),
}
go task.Run()
return task
}
func main() {
var tasks [10]task
// 创建任务出来,同时任务一直会消费数据
for i := 0; i < 10; i++ {
tasks[i] = createTask()
}
// 模拟塞进十个任务进去
for i := 0; i < 10; i++ {
data := fmt.Sprintf("前端任务【%d】", i)
tasks[i].data <- data
<-tasks[i].done
}
// 模拟塞进十个任务进去
for i := 0; i < 10; i++ {
data := fmt.Sprintf("后端任务【%d】", i)
tasks[i].data <- data
<-tasks[i].done
}
}
执行结果如下
- 我们希望前端任务先全部做完,然后后端任务全部做完,然后继续做别的事情
package main
import "fmt"
type task struct {
data chan string
done chan bool
}
func (t task) Run() {
for v := range t.data {
fmt.Printf("current data = %s\n", v)
t.done <- true
}
}
func createTask() task {
task := task{
data: make(chan string, 1),
done: make(chan bool, 1),
}
go task.Run()
return task
}
func main() {
var tasks [10]task
// 创建任务出来,同时任务一直会消费数据
for i := 0; i < 10; i++ {
tasks[i] = createTask()
}
// 模拟塞进十个任务进去
for i := 0; i < 10; i++ {
data := fmt.Sprintf("前端任务【%d】", i)
tasks[i].data <- data
}
for i := 0; i < 10; i++ {
<-tasks[i].done
}
// 模拟塞进十个任务进去
for i := 0; i < 10; i++ {
data := fmt.Sprintf("后端任务【%d】", i)
tasks[i].data <- data
}
for i := 0; i < 10; i++ {
<-tasks[i].done
}
}
执行结果如下
- 我们希望前端任务和后端任务交替并发执行
package main
import "fmt"
type task struct {
data chan string
done chan bool
}
func (t task) Run() {
for v := range t.data {
fmt.Printf("current data = %s\n", v)
t.done <- true
}
}
func createTask() task {
task := task{
data: make(chan string, 1),
done: make(chan bool, 1),
}
go task.Run()
return task
}
func main() {
var tasks [10]task
// 创建任务出来,同时任务一直会消费数据
for i := 0; i < 10; i++ {
tasks[i] = createTask()
}
// 模拟塞进十个任务进去
for i := 0; i < 10; i++ {
data := fmt.Sprintf("前端任务【%d】", i)
tasks[i].data <- data
}
// 模拟塞进十个任务进去
for i := 0; i < 10; i++ {
data := fmt.Sprintf("后端任务【%d】", i)
tasks[i].data <- data
}
for i := 0; i < 10; i++ {
<-tasks[i].done
<-tasks[i].done
}
}
执行结果如下