有缓冲的channel

package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

const (
	numberGoroutines = 4
	taskLoad         = 10
)

var wg sync.WaitGroup

func init() {
	rand.Seed(time.Now().Unix())
}

func worker(tasks chan string, worker int) {
	defer wg.Done()
	for {
		task, ok := <-tasks
		if !ok {
			fmt.Printf("Workerr : %d : Shutting Down\n", worker)
			return
		}
		fmt.Printf("Worker : %d : Started work %s\n", worker, task)
		sleep := rand.Int63n(100)
		time.Sleep(time.Duration(sleep) * time.Microsecond)
		fmt.Printf("Worker : %d : Completed %s \n", worker, task)
	}
}

func main() {
	tasks := make(chan string, 10)
	wg.Add(numberGoroutines)
	// 开启消费者
	for gr := 1; gr <= numberGoroutines; gr++ {
		go worker(tasks, gr)
	}
	// 开启生产者
	for post := 1; post <= taskLoad; post++ {
		tasks <- fmt.Sprintf("Task : %d", post)
	}
	close(tasks)
	wg.Wait()
}

有一个疑问就是,如果tasks这个channel换成无缓冲的,结果看上去有点奇怪

<think>嗯,用户想了解Go语言中有缓冲区和无缓冲区的channel的区别及使用场景。首先,我需要回忆一下这两种channel的基本概念。根据引用[2]和[5],无缓冲channel在声明时缓冲区大小是0,发送和接收操作会直接阻塞,直到另一方准备好。这种同步特性适合用于goroutine之间的精确协调,比如确保某个任务完成后再进行下一步。 然后,带缓冲channel允许在缓冲区未满时发送不阻塞,接收同理。引用[1]提到,缓冲channel可以减轻同步压力,提高并行性。比如在数据收集或者批量处理时,生产者可以连续发送多个数据,而消费者可以稍后处理,避免频繁的阻塞。引用[4]中的批量处理场景就是一个很好的例子,缓冲channel能暂存数据,提高吞吐量。 接下来要区分两者的区别。无缓冲channel强调即时通信,发送和接收必须同时准备好,否则会阻塞,这类似于同步队列。而缓冲channel允许一定程度的异步,发送方可以在缓冲区未满时继续发送,接收方也可以在缓冲区有数据时接收,减少阻塞时间。根据引用[3]中的hchan结构,缓冲区是通过循环队列实现的,sendx和recvx记录位置,当缓冲区满或空时,才会阻塞对应的goroutine。 使用场景方面,无缓冲适合严格的同步场景,比如任务协调或事件通知。比如等待一个goroutine完成初始化后再继续主流程。而缓冲channel适用于需要缓冲数据的情况,如高并发下的请求处理,生产者-消费者模型中的批量处理,或者流量控制,通过调整缓冲区大小平衡生产和消费速度。 性能方面,引用[2]提到适当设置缓冲区大小可以改善高负载下的吞吐量和响应时间。但过大的缓冲区可能导致内存占用过高,需权衡。例如,引用中的例子创建了缓冲大小为1000的channel,允许发送方快速填充,而接收方按需处理。 另外,错误处理需要注意。比如关闭channel后发送数据会panic,无论是否带缓冲。而带缓冲channel在关闭后仍可读取剩余数据,直到缓冲区为空。这在引用[1]的示例中有所体现,关闭channel后仍能读取缓冲区中的数据。 总结时,需要明确两者的核心区别在于同步与异步,以及各自适用的场景。同时,结合引用中的例子和原理,说明如何在实际编码中选择合适的channel类型。可能还需要提到缓冲区的容量设置对程序性能的影响,如引用[2]中的建议,根据负载调整缓冲区大小。</think>### Go语言 Channel 缓冲区与非缓冲区的区别及使用场景 #### 一、核心区别 1. **同步机制** - **无缓冲Channel**(`make(chan T)`):发送和接收操作**必须同步完成**。发送方会阻塞直到接收方准备好读取数据,反之亦然[^5]。 - **带缓冲Channel**(`make(chan T, n)`):缓冲区允许发送和接收操作**异步执行**。发送方仅在缓冲区满时阻塞,接收方仅在缓冲区空时阻塞[^2]。 2. **底层实现** - 无缓冲Channel通过直接传递数据实现同步(类似队列长度为0)[^3]。 - 带缓冲Channel通过循环队列(`hchan`结构中的`buf`字段)存储数据,队列长度由`dataqsiz`字段定义[^3]。 #### 二、使用场景对比 | 类型 | 适用场景 | 示例 | |---------------|--------------------------------------------------------------------------|--------------------------------------------------------------------------------| | **无缓冲** | 需要严格同步的通信(如事件通知、任务协调) | 等待goroutine完成初始化后触发后续操作 | | **带缓冲** | 需要缓冲数据以平衡生产/消费速度(如批量处理、高并发请求队列) | 日志收集系统暂存日志后批量写入数据库[^4] | #### 三、代码示例 1. **无缓冲Channel** ```go func main() { ch := make(chan int) // 无缓冲 go func() { time.Sleep(2 * time.Second) <-ch // 接收解除发送阻塞 }() ch <- 1 // 发送阻塞直到接收完成 fmt.Println("Sent") } ``` 2. **带缓冲Channel** ```go func main() { ch := make(chan int, 3) // 缓冲区容量3 go func() { for i := 0; i < 5; i++ { ch <- i // 前3次不阻塞,后2次阻塞 } close(ch) }() for v := range ch { fmt.Println(v) // 消费速度慢于生产时,缓冲区缓解阻塞 } } ``` #### 四、性能与注意事项 1. **缓冲区大小选择** - 缓冲区过小可能导致频繁阻塞(如高并发场景下)。 - 缓冲区过大可能占用过多内存(引用[2]建议根据负载测试调整)。 2. **错误处理** - 向已关闭的Channel发送数据会触发panic(无论是否带缓冲)。 - 带缓冲Channel关闭后仍可读取剩余数据[^1]。 #### 五、典型应用 1. **无缓冲** - 控制并发流程(如限制最大并行任务数)[^5]。 - 精确同步(如等待多个goroutine完成)。 2. **带缓冲** - 流量削峰(如处理突发请求)[^4]。 - 批量聚合数据(如日志缓冲写入)。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值