Golang并发编程学习指南

Golang并发编程学习指南 大家好,

正在学习并发编程,想了解通道和等待组的使用场景。

// var wait sync.WaitGroup

type Post struct {
	ID   int
	Text string
}

func main() {

	var posts []Post

	p1 := CreatePost(1, "yahoo")
	p2 := CreatePost(2, "google 1")
	p3 := CreatePost(3, "excite 2")
	p4 := CreatePost(4, "altavista 3")
	p5 := CreatePost(5, "bing 4")
	posts = append(posts, p1, p2, p3, p4, p5)

    postCh := make(chan Post, 5)
	go GetPosts(postCh, posts)

	for v := range postCh {
		fmt.Println(v)
	}

}

func GetPosts(c chan Post, po []Post) {

	for _, v := range po {

		c <- v
	}

	close(c)

}

func CreatePost(id int, text string) Post {

	return Post{
		ID:   id,
		Text: text,
	}

}

更多关于Golang并发编程学习指南的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

通道的用例:在goroutine之间传递值(而不是在goroutine之间共享内存)。

无缓冲通道的用例:在两个goroutine之间提供同步。(发送方会阻塞,直到接收方准备好接收发送的元素。)

WaitGroups的用例:生成多个goroutine并等待它们全部完成。

当然,你可以用通道替代WaitGroups,也可以用受互斥锁保护的共享变量访问来替代通道。但通道和等待组更易于阅读、理解和推理。

更多关于Golang并发编程学习指南的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


以下是针对您代码中通道和等待组使用场景的专业分析,以及改进后的示例代码。

通道和等待组的使用场景

通道(channel) 主要用于goroutine之间的数据传递和同步,特别适合生产者-消费者模式。等待组(sync.WaitGroup) 则用于等待一组goroutine完成执行。

在您的代码中,虽然使用了通道来传递数据,但缺少对goroutine执行完成的同步控制。以下是改进版本:

package main

import (
	"fmt"
	"sync"
	"time"
)

type Post struct {
	ID   int
	Text string
}

func main() {
	var posts []Post
	var wg sync.WaitGroup

	p1 := CreatePost(1, "yahoo")
	p2 := CreatePost(2, "google 1")
	p3 := CreatePost(3, "excite 2")
	p4 := CreatePost(4, "altavista 3")
	p5 := CreatePost(5, "bing 4")
	posts = append(posts, p1, p2, p3, p4, p5)

	// 创建带缓冲的通道
	postCh := make(chan Post, 5)
	
	// 启动生产者goroutine
	wg.Add(1)
	go ProducePosts(postCh, posts, &wg)

	// 启动消费者goroutine
	wg.Add(1)
	go ConsumePosts(postCh, &wg)

	// 等待所有goroutine完成
	wg.Wait()
	fmt.Println("所有处理完成")
}

// 生产者函数 - 向通道发送数据
func ProducePosts(c chan<- Post, po []Post, wg *sync.WaitGroup) {
	defer wg.Done()
	defer close(c) // 确保通道被关闭

	for _, v := range po {
		// 模拟处理时间
		time.Sleep(100 * time.Millisecond)
		c <- v
		fmt.Printf("生产: %v\n", v)
	}
}

// 消费者函数 - 从通道接收数据
func ConsumePosts(c <-chan Post, wg *sync.WaitGroup) {
	defer wg.Done()

	for v := range c {
		// 模拟处理时间
		time.Sleep(50 * time.Millisecond)
		fmt.Printf("消费: ID=%d, Text=%s\n", v.ID, v.Text)
	}
}

func CreatePost(id int, text string) Post {
	return Post{
		ID:   id,
		Text: text,
	}
}

更复杂的并发处理示例

// 使用多个生产者和消费者的示例
func main() {
	var wg sync.WaitGroup
	postCh := make(chan Post, 10)
	
	// 创建测试数据
	posts := []Post{
		CreatePost(1, "Post 1"),
		CreatePost(2, "Post 2"),
		CreatePost(3, "Post 3"),
		CreatePost(4, "Post 4"),
		CreatePost(5, "Post 5"),
	}

	// 启动2个生产者
	wg.Add(2)
	go func() {
		defer wg.Done()
		for i := 0; i < 3; i++ {
			postCh <- posts[i]
		}
	}()
	
	go func() {
		defer wg.Done()
		for i := 3; i < 5; i++ {
			postCh <- posts[i]
		}
	}()

	// 启动3个消费者
	wg.Add(3)
	for i := 0; i < 3; i++ {
		go func(workerID int) {
			defer wg.Done()
			for post := range postCh {
				fmt.Printf("Worker %d 处理: %v\n", workerID, post)
			}
		}(i)
	}

	// 等待生产者完成并关闭通道
	go func() {
		wg.Wait()
		// 注意:这里需要小心处理wg的状态
		close(postCh)
	}()

	// 等待所有消费者完成
	wg.Wait()
}

关键要点

  1. 通道方向: 使用 chan<-<-chan 明确通道的读写方向
  2. 等待组传递: 等待组必须通过指针传递 &wg
  3. 资源清理: 使用 defer close(c) 确保通道被正确关闭
  4. 缓冲选择: 根据数据量大小合理设置通道缓冲区大小

这种模式确保了数据的正确传递和goroutine的同步执行,避免了数据竞争和资源泄漏。

回到顶部