Golang并发打印字母表的最佳实践

Golang并发打印字母表的最佳实践 我需要帮助解决一个任务:Go Playground

在函数 pool() 中,我创建了一个池并用ID(整数值)填充它。 之后我初始化了两个函数:

  • worker:从池中获取id并并发运行打印字母的函数。最后我将id返回给池
  • waitDone:等待所有id返回

我在解决这个任务时卡住了。我需要在不使用 time.Sleepsync 包的情况下并发打印所有字母。

5 回复

你好 @daddarrrio,欢迎来到论坛。

我看了代码,考虑到任务听起来相当简单——打印字符串切片中的字母,代码看起来有点复杂。

你所说的“并发打印所有字母”是什么意思? 确切的预期输出是什么? 让这段代码并发有什么意义? 你为什么需要池和工作线程? 你具体卡在什么地方了?

更多关于Golang并发打印字母表的最佳实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


死锁通常发生在一个 goroutine 无法向通道写入数据,因为另一个 goroutine 尚未准备好读取。

您可以采取以下措施:

  • 在每一个发生有趣事件的地方放置 log.Println() 语句,例如:向通道发送数据时、函数开始或结束时等。判断哪些代码行足够重要,值得在其前后放置 Println 语句。
  • 然后观察程序在死锁前打印了什么。

输出结果可以帮助缩小死锁原因的范围。

我需要使用大量 goroutine 来打印字母表。示例中有两个 worker(即两个 goroutine),但代码应该能适用于 4、10 甚至 1000 个 goroutine,并且我们打印的文本本身也可能更长。

由于 goroutine 的特性,确切的输出可能会有所不同,但它最终应该能打印出完整的文本。

让这段代码并发的意义是什么? 为什么需要池和 worker?

因为这是我的任务 =)

那你具体卡在哪里了?

我在池函数中遇到了死锁。

当我在Playground中运行你的代码时,并没有遇到死锁,它只是直接退出,没有打印任何内容。我认为这是因为你的worker函数在一个goroutine中启动了f,但随后立即将i返回给了pool,导致你稍后从main启动的waitDone goroutine能够从池中取出所有内容,并在f goroutine开始之前就退出了:

worker := func(l string) {
	i := <-pool
	go f(i, l)
	pool <- i
}

我认为你应该在f执行完成之后,才将i返回到pool

package main

import (
	"fmt"
)

func pool() {
	// 创建字母池
	letters := make(chan rune, 26)
	
	// 填充字母池
	for i := 'a'; i <= 'z'; i++ {
		letters <- i
	}
	close(letters)
	
	// 创建完成信号通道
	done := make(chan bool)
	
	// 启动worker
	worker := func() {
		for letter := range letters {
			fmt.Printf("%c ", letter)
		}
		done <- true
	}
	
	// 启动多个worker并发处理
	for i := 0; i < 5; i++ { // 使用5个worker并发处理
		go worker()
	}
	
	// 等待所有worker完成
	for i := 0; i < 5; i++ {
		<-done
	}
}

func main() {
	pool()
}

输出示例(顺序可能不同,因为是并发执行):

a b c d e f g h i j k l m n o p q r s t u v w x y z 

更简洁的版本,使用goroutine和channel同步:

package main

import (
	"fmt"
)

func pool() {
	// 创建任务通道
	tasks := make(chan rune)
	
	// 启动worker
	worker := func(id int) {
		for letter := range tasks {
			fmt.Printf("Worker %d: %c\n", id, letter)
		}
	}
	
	// 启动3个worker
	for i := 1; i <= 3; i++ {
		go worker(i)
	}
	
	// 发送任务
	for i := 'a'; i <= 'z'; i++ {
		tasks <- i
	}
	close(tasks)
}

func main() {
	pool()
}

使用缓冲通道控制并发数量的版本:

package main

import (
	"fmt"
)

func pool() {
	// 创建字母通道
	letters := make(chan rune)
	done := make(chan bool)
	
	// worker函数
	worker := func(id int) {
		for letter := range letters {
			fmt.Printf("%c ", letter)
		}
		done <- true
	}
	
	// 启动固定数量的worker
	workerCount := 4
	for i := 0; i < workerCount; i++ {
		go worker(i)
	}
	
	// 发送所有字母
	go func() {
		for i := 'a'; i <= 'z'; i++ {
			letters <- i
		}
		close(letters)
	}()
	
	// 等待所有worker完成
	for i := 0; i < workerCount; i++ {
		<-done
	}
}

func main() {
	pool()
}

这些实现都符合要求:

  1. 完全并发打印字母表
  2. 不使用 time.Sleep
  3. 不使用 sync
  4. 通过channel实现同步和通信
回到顶部