Go channel详解

无缓冲的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,否则在我们在循环的时候会进入死锁状态

  1. 通过 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)
}
  1. 通过 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协程还没有机会执行,下面是几种等待协程结束的方案

  1. 利用 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()
}
  1. 利用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等待的几种情形

场景:比如我们现在要做十个前端任务和十个后端任务,我们有以下需求

  1. 我们希望前端任务顺序执行,后端任务顺序执行
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
	}
}

执行结果如下
在这里插入图片描述

  1. 我们希望前端任务先全部做完,然后后端任务全部做完,然后继续做别的事情
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
	}
}

执行结果如下
在这里插入图片描述

  1. 我们希望前端任务和后端任务交替并发执行
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
	}
}

执行结果如下
在这里插入图片描述

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值