Golang协程中channel和sync的角色扮演分析
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">Channel</a></li><ul class="second_class_ul"><li><a href="#_lab2_0_0">代码示例</a></li></ul><li><a href="#_label1">Sync</a></li><ul class="second_class_ul"><li><a href="#_lab2_1_1">代码示例</a></li></ul><li><a href="#_label2">实际应用</a></li><ul class="second_class_ul"><li><a href="#_lab2_2_2">问题代码</a></li><li><a href="#_lab2_2_3">问题分析</a></li><li><a href="#_lab2_2_4">解决方案</a></li><ul class="third_class_ul"><li><a href="#_label3_2_4_0">Mutex(互斥锁)解决案例</a></li><li><a href="#_label3_2_4_1">Channel 解决案例</a></li></ul></ul><li><a href="#_label3">结论</a></li><ul class="second_class_ul"><li><a href="#_lab2_3_5">Channel 的角色</a></li><ul class="third_class_ul"></ul><li><a href="#_lab2_3_6">Sync 的角色</a></li><ul class="third_class_ul"></ul></ul></ul></div><p class="maodian"><a name="_label0"></a></p><h2>Channel</h2><blockquote><p>channel 是Go语言中用于协程(goroutine)之间通信的一种机制。通过channel,你可以在不同的goroutine之间安全地传递数据。</p></blockquote>
<p class="maodian"><a name="_lab2_0_0"></a></p><p class="maodian"><a name="_lab2_1_1"></a></p><h3>代码示例</h3>
<div class="jb51code"><pre class="brush:go;">package main
import (
"fmt"
"strconv"
"time"
)
func main() {
fmt.Println("主进程开始")
done := make(chan bool, 5)
for i := 0; i < 5; i++ {
fmt.Println("等待进协程:" + strconv.Itoa(i))
go worker(i, done)
}
for i := 0; i < 5; i++ {
fmt.Println("等待出协程:" + strconv.Itoa(i))
<-done
fmt.Println("已经出协程:" + strconv.Itoa(i))
}
close(done)
fmt.Println("主进程结束")
}
func worker(i int, done chan bool) {
time.Sleep(2 * time.Second)
fmt.Println("协程:" + strconv.Itoa(i))
done <- true
}</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202601/2026010409111195.png" /></p>
<p><a href="https://www.jb51.net/article/210620.htm" target="_blank">Golang缓冲channel和非缓冲channel的区别说明</a></p>
<p class="maodian"><a name="_label1"></a></p><h2>Sync</h2>
<blockquote><p>sync 包提供了基本的同步原语,用于在多个goroutine之间协调对共享资源的访问。</p></blockquote>
<h3>代码示例</h3>
<div class="jb51code"><pre class="brush:go;">package main
import (
"fmt"
"strconv"
"sync"
"time"
)
func main() {
fmt.Println("主进程开始")
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
fmt.Println("等待进协程:" + strconv.Itoa(i))
wg.Add(1)
go worker(i, &wg)
}
fmt.Println("主进程等待")
wg.Wait()
fmt.Println("主进程结束")
}
func worker(i int, wg *sync.WaitGroup) {
defer wg.Done()
time.Sleep(2 * time.Second)
fmt.Println("协程:" + strconv.Itoa(i))
}</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202601/2026010409111115.png" /></p>
<p class="maodian"><a name="_label2"></a></p><h2>实际应用</h2>
<p class="maodian"><a name="_lab2_2_2"></a></p><h3>问题代码</h3>
<div class="jb51code"><pre class="brush:go;">package main
import (
"fmt"
"sync"
)
// 定义结构体
type MyStruct struct {
Field1 string
Field2 int
}
func main() {
var myStructs []MyStruct
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
fmt.Println(i)
go worker(&myStructs, &wg, i)
}
wg.Wait()
fmt.Println(myStructs)
fmt.Println(len(myStructs))
}
func worker(data *[]MyStruct, wg *sync.WaitGroup, i int) {
defer wg.Done()
ss := MyStruct{
Field1: fmt.Sprintf("Data %d", i),
Field2: i * 10,
}
*data = append(*data, ss)
}</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202601/2026010409111111.png" /></p>
<p class="maodian"><a name="_lab2_2_3"></a></p><h3>问题分析</h3>
<p>多个 worker 协程并发地向共享切片 myStructs 添加元素时存在竞态风险。由于 Go 语言的切片并非并发安全,当多个协程同时调用 append 函数修改切片时,可能导致数据竞争或意外行为。</p>
<p class="maodian"><a name="_lab2_2_4"></a></p><h3>解决方案</h3>
<p class="maodian"><a name="_label3_2_4_0"></a></p><h4>Mutex(互斥锁)解决案例</h4>
<p><a href="https://www.jb51.net/article/272121.htm" rel="external nofollow" target="_blank">Golang语言中的互斥锁和读写锁(Mutex和RWMutex)</a></p>
<div class="jb51code"><pre class="brush:go;">package main
import (
"fmt"
"sync"
)
// 定义结构体
type MyStruct struct {
Field1 string
Field2 int
}
func main() {
var myStructs []MyStruct
var mu sync.Mutex
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go worker(&myStructs, &wg, &mu, i)
}
wg.Wait()
fmt.Println(myStructs)
fmt.Println(len(myStructs))
}
func worker(data *[]MyStruct, wg *sync.WaitGroup, mu *sync.Mutex, i int) {
defer wg.Done()
ss := MyStruct{
Field1: fmt.Sprintf("Data %d", i),
Field2: i * 10,
}
mu.Lock()
*data = append(*data, ss)
mu.Unlock()
}</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202601/2026010409111165.png" /></p>
<p>问题虽已解决,但Mutex在高并发场景下会导致大量锁等待,对系统性能造成显著影响。</p>
<p class="maodian"><a name="_label3_2_4_1"></a></p><h4>Channel 解决案例</h4>
<div class="jb51code"><pre class="brush:go;">package main
import (
"fmt"
"sync"
)
type MyStruct struct {
Field1 string
Field2 int
}
func main() {
var wg sync.WaitGroup
dataChan := make(chan MyStruct, 10)
for i := 0; i < 10; i++ {
wg.Add(1)
go worker(dataChan, &wg, i)
}
go func() {
wg.Wait()
close(dataChan)
}()
var myStructs []MyStruct
for ss := range dataChan {
myStructs = append(myStructs, ss)
}
fmt.Println(myStructs)
fmt.Println(len(myStructs))
}
func worker(dataChan chan<- MyStruct, wg *sync.WaitGroup, i int) {
defer wg.Done()
ss := MyStruct{
Field1: fmt.Sprintf("Data %d", i),
Field2: i * 10,
}
dataChan <- ss
}</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202601/2026010409111165.png" /></p>
<p class="maodian"><a name="_label3"></a></p><h2>结论</h2>
<p class="maodian"><a name="_lab2_3_5"></a></p><h3>Channel 的角色</h3>
<p>channel是Go语言中协程(goroutine)之间通信的桥梁,它有以下重要作用:</p>
<ul><li>协程间通信:channel提供了一种在goroutine之间安全地传递数据的方式。通过使用channel,goroutine可以相互发送和接收数据,从而实现协作和协调。</li><li>同步与协调:channel的阻塞特性使得goroutine之间的同步变得简单而直观。当一个goroutine试图从一个没有数据的channel中接收数据时,它会阻塞直到有数据可用。同样,当一个goroutine试图向一个已经满的channel发送数据时,它也会被阻塞。这种阻塞机制使得goroutine能够按照预期的顺序进行通信和协作。</li><li>避免共享状态:通过使用channel进行通信,可以减少对共享状态的依赖,从而降低并发编程中的复杂性和错误率。每个goroutine可以拥有自己的局部变量和状态,并通过channel与其他goroutine进行交互,而无需直接访问共享内存。</li></ul>
<p class="maodian"><a name="_lab2_3_6"></a></p><h3>Sync 的角色</h3>
<p>sync包提供了一组同步原语,用于保护共享资源和协调goroutine的执行,确保并发程序的正确性和一致性。sync包中的关键组件包括:</p>
<ul><li>互斥锁(Mutex):sync.Mutex是一个互斥锁,用于保护共享资源,确保同一时间只有一个goroutine可以访问该资源。通过加锁和解锁操作,可以防止多个goroutine同时修改共享资源,从而避免竞态条件和数据不一致。</li><li>读写锁(RWMutex):sync.RWMutex是互斥锁的一种扩展,允许多个goroutine同时读取共享资源,但在写入时阻塞所有其他goroutine(无论是读还是写)。这种读写分离的机制可以提高并发程序的性能,特别是在读多写少的场景中。</li><li>等待组(WaitGroup):sync.WaitGroup提供了一种等待一组goroutine完成的方法。通过调用Add方法增加等待组的计数,并在每个goroutine结束时调用Done方法减少计数,主goroutine可以调用Wait方法阻塞,直到所有goroutine都完成执行。这有助于协调goroutine的执行顺序和依赖关系。</li><li>条件变量(Cond):sync.Cond是一个条件变量,允许一个或多个goroutine等待某个条件为真,并在该条件变为真时通知它们。条件变量提供了一种更灵活的同步机制,适用于需要根据特定条件进行等待和通知的场景。</li></ul>
<p><strong>综上所述,channel和sync包在Go语言的并发编程中各自扮演着重要的角色。channel用于goroutine之间的通信和同步,而sync包提供了保护共享资源和协调goroutine执行的同步原语。它们共同确保了并发程序的正确性和高效性。</strong></p>
頁:
[1]