Golang中如何通过channel实现资源通信?
Golang中如何通过channel实现资源通信? 你好…
能否请你解释一下这句话?
“不要通过共享内存来通信;相反,要通过通信来共享内存。”
我来自基于线程的编程范式,该范式使用锁来竞争共享资源…我仍然对上述说法如何解决这个问题感到困惑…我真的需要理解这一点才能充分利用 Go 的并发模型…我想象有 8 个线程在竞争一个共享资源,所以所有线程都会等待,只有一个线程可以锁定共享资源,而其他 7 个线程将等待…
我想理解上述说法如何能在无竞争的情况下共享数据…我仍然无法理解…你能给我一个简单的代码示例,展示如何在无竞争的情况下共享数据吗?(在 goroutine 之间)
c/c++ 背景
这是 Go 中最重要的概念,我想理解它…
此致
更多关于Golang中如何通过channel实现资源通信?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
当通过"共享内存进行通信"时,存在对共享内存的并发访问,您必须努力保护这种访问。这就是您提到的8个线程的例子。当通过"通信共享内存"时,您将值(在内存中,因此您共享了内存)发送到通道(通信)。因此这些发送操作通过通道自然地被序列化,接收方所做的任何修改都将是原子性的、并发安全的(如果正确实现)。
更多关于Golang中如何通过channel实现资源通信?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
“你在传递值(在内存中,所以你共享了内存)”
这一点很重要,所以你说仅仅传递值就不会产生竞争…但如果我也想进行修改呢?我能通过通道传递引用吗?或者这不是正确的做法(可以把它想象成传递状态)…你能分享一个示例链接吗?我会尝试研究代码…从基于线程的模型转变思维确实很困难,就像你一生都习惯于这种思维模式,当有人告诉你存在更好的方法时…你很难彻底理解它…
如果你将原始值的指针传递给通道,可能会遇到竞态问题。请记住,当你传递指针或引用时,实际上是在发布它,你无法预知这个"属于自己"的对象会经历多少次修改(实际上,一旦发布它就不再属于你了)。更严重的是,接收方若编写了不当的并发代码来修改你的对象,可能导致灾难性后果(别忘了,一旦你提供引用,对方就能并发修改你的对象)。因此最佳实践是传递副本(Go语言天然支持这一特性)。若需查看具体示例,请参考生产者/消费者问题:http://www.golangpatterns.info/concurrency/producer-consumer
祝您有美好的一天。
在Go语言中,“不要通过共享内存来通信;相反,要通过通信来共享内存” 这一理念是并发编程的核心。让我通过具体代码示例来解释这个概念。
传统共享内存方式(有竞争)
在基于锁的编程中,多个goroutine直接访问共享变量:
package main
import (
"fmt"
"sync"
"time"
)
// 传统方式:使用锁保护共享内存
func traditionalSharedMemory() {
var counter int
var mutex sync.Mutex
var wg sync.WaitGroup
for i := 0; i < 8; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
mutex.Lock()
// 临界区 - 只有一个goroutine能进入
counter++
fmt.Printf("Goroutine %d: counter = %d\n", id, counter)
mutex.Unlock()
}(i)
}
wg.Wait()
fmt.Printf("Final counter: %d\n", counter)
}
这种方式确实存在竞争,所有goroutine都在等待锁。
Go的方式:通过通信共享内存
package main
import (
"fmt"
"time"
)
// Go方式:通过channel通信来共享数据
func goStyleCommunication() {
// 创建一个channel用于通信
counterChan := make(chan int)
done := make(chan bool)
// 启动一个goroutine来管理共享状态
go func() {
counter := 0
for {
select {
case op := <-counterChan:
// 接收操作请求,安全地更新计数器
counter += op
fmt.Printf("Counter updated: %d\n", counter)
case <-done:
return
}
}
}()
// 启动8个goroutine,通过channel发送操作请求
for i := 0; i < 8; i++ {
go func(id int) {
// 发送增量操作,而不是直接访问共享变量
counterChan <- 1
fmt.Printf("Goroutine %d sent increment request\n", id)
}(i)
}
// 等待所有操作完成
time.Sleep(1 * time.Second)
done <- true
close(counterChan)
}
更实用的示例:工作池模式
package main
import (
"fmt"
"sync"
)
type Resource struct {
data int
}
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
// 处理任务,没有竞争
result := job * 2
fmt.Printf("Worker %d processed job %d, result: %d\n", id, job, result)
results <- result
}
}
func main() {
const numWorkers = 4
const numJobs = 8
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
// 启动worker池
for i := 1; i <= numWorkers; i++ {
wg.Add(1)
go worker(i, jobs, results, &wg)
}
// 发送任务
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
// 收集结果
go func() {
wg.Wait()
close(results)
}()
// 处理结果
var total int
for result := range results {
total += result
}
fmt.Printf("Total result: %d\n", total)
}
关键区别
- 传统方式:多个goroutine直接竞争访问共享变量
- Go方式:只有一个goroutine拥有数据所有权,其他goroutine通过channel发送操作请求
在Go的方式中:
- 数据所有权明确
- 没有锁竞争
- 通过消息传递协调
- 更易于理解和维护
这种方式消除了数据竞争,因为只有一个goroutine实际访问数据,其他goroutine只是发送操作请求。

