Golang中goroutines使用遇到的问题
Golang中goroutines使用遇到的问题 大家好, 我是新手,仍在学习Go语言。 我有一个用于下载多个git仓库的脚本。为了加速这个过程,我使用了goroutine。我创建了另一个简短的脚本来模拟我的问题。
以下脚本在等待每个goroutine中的随机时间的同时创建多个文件夹和子文件夹。
package main
import (
"fmt"
"math/rand"
"os"
"strconv"
"sync"
"time"
)
const (
xthreads = 5 // 要使用的线程总数,不包括 main() 线程
folderCount = 50 // 需要创建的最大文件夹数
)
func doSomething1(a int) {
fmt.Println("doSomething1:", a)
if err := os.Mkdir("testDir"+strconv.Itoa(a), 0755); err != nil {
fmt.Println(err)
}
}
func doSomething2(a int) {
fmt.Println("doSomething2:", a)
if err := os.Chdir("testDir" + strconv.Itoa(a)); err != nil {
fmt.Println(err)
}
if err := os.Mkdir("anotherDir", 0755); err != nil {
fmt.Println(err)
}
if err := os.Chdir(".."); err != nil {
fmt.Println(err)
}
}
func main() {
var ch = make(chan int)
var wg sync.WaitGroup
var dir string
var err error
if dir, err = os.MkdirTemp(os.TempDir(), "test-"); err != nil {
fmt.Println(err)
}
if err = os.Chdir(dir); err != nil {
fmt.Println(err)
}
// 这启动了 xthreads 个 goroutine,等待执行任务
wg.Add(xthreads)
for i := 0; i < xthreads; i++ {
go func() {
for {
a, ok := <-ch
if !ok { // 如果无事可做且通道已关闭,则结束 goroutine
wg.Done()
return
}
doSomething1(a)
doSomething2(a)
rand.Seed(time.Now().UnixNano())
randomSleep := time.Duration(rand.Intn(10))
time.Sleep(randomSleep * time.Second)
fmt.Printf("Sleep for %d second...\n", randomSleep)
}
}()
}
// 现在可以将任务添加到通道中,该通道用作队列
for i := 0; i < folderCount; i++ {
ch <- i // 将 i 添加到队列
}
close(ch) // 这告诉 goroutines 没有其他事情可做
wg.Wait() // 等待线程完成
}
我想要实现的目标是:
- 例如,同一时间只应执行5个goroutine
xthreads - 当其中一个完成后,另一个必须启动,这样我就可以一直有5个goroutine同时运行,直到达到
folderCount值 - 文件夹结构应如下所示:
/tmp/test-2325124430
├── testDir0
│ └── anotherDir
├── testDir1
│ └── anotherDir
├── testDir2
│ └── anotherDir
├── testDir3
│ └── anotherDir
└── testDir4
└── anotherDir
...................
...................
└── testDir50
└── anotherDir
等等…
但问题是,当我启动脚本时,它创建的文件夹如下:
/tmp/test-2325124430
├── anotherDir
├── testDir0
│ └── anotherDir
├── testDir1
├── testDir2
│ └── anotherDir
├── testDir3
│ └── anotherDir
└── testDir4
└── anotherDir
...................
/tmp
├── testDir16
├── anotherDir
│ └── testDir17
...................
/
├── testDir18
├── testDir19
│ └── testDir20
...................
控制台输出是:
go run -race test.go
doSomething1: 3
doSomething1: 1
doSomething2: 3
doSomething2: 1
doSomething1: 4
doSomething1: 0
doSomething2: 0
doSomething1: 2
doSomething2: 2
doSomething2: 4
chdir testDir1: no such file or directory
Sleep for 1 second...
doSomething1: 5
doSomething2: 5
Sleep for 0 second...
doSomething1: 6
doSomething2: 6
Sleep for 4 second...
doSomething1: 7
doSomething2: 7
Sleep for 5 second...
doSomething1: 8
doSomething2: 8
Sleep for 7 second...
doSomething1: 9
doSomething2: 9
Sleep for 3 second...
doSomething1: 10
doSomething2: 10
Sleep for 1 second...
doSomething1: 11
doSomething2: 11
Sleep for 9 second...
doSomething1: 12
doSomething2: 12
Sleep for 9 second...
doSomething1: 13
doSomething2: 13
Sleep for 9 second...
doSomething1: 14
doSomething2: 14
Sleep for 7 second...
doSomething1: 15
doSomething2: 15
Sleep for 7 second...
doSomething1: 16
Sleep for 6 second...
doSomething2: 16
doSomething1: 17
doSomething2: 17
chdir testDir17: no such file or directory
Sleep for 8 second...
doSomething1: 18
doSomething2: 18
Sleep for 6 second...
doSomething1: 19
doSomething2: 19
Sleep for 6 second...
Sleep for 7 second...
Sleep for 6 second...
Sleep for 7 second...
Sleep for 8 second...
更多关于Golang中goroutines使用遇到的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html
3 回复
是的,我确信如果我将 os.Chdir 改为某种能跟踪整个路径的方法,就一定能正常工作。我之前不知道 os.Chdir 有这种行为,文档里没有提到。
谢谢
更多关于Golang中goroutines使用遇到的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
os.Chdir 会改变整个进程的工作目录;它并非针对特定的 goroutine(或“线程”)。
与其在你的 goroutine 中改变目录,不如记录下你希望每个 goroutine 使用的路径,并直接使用这些路径;不要改变目录。
你的代码存在两个主要问题:目录切换的竞态条件和随机数种子重复初始化。以下是修正后的版本:
package main
import (
"fmt"
"math/rand"
"os"
"path/filepath"
"strconv"
"sync"
"time"
)
const (
xthreads = 5 // 并发goroutine数量
folderCount = 50 // 需要创建的文件夹总数
)
func doSomething1(baseDir string, a int) error {
dirName := "testDir" + strconv.Itoa(a)
dirPath := filepath.Join(baseDir, dirName)
fmt.Println("doSomething1:", a)
if err := os.Mkdir(dirPath, 0755); err != nil {
return fmt.Errorf("创建目录 %s 失败: %v", dirPath, err)
}
return nil
}
func doSomething2(baseDir string, a int) error {
dirName := "testDir" + strconv.Itoa(a)
dirPath := filepath.Join(baseDir, dirName)
fmt.Println("doSomething2:", a)
anotherDir := filepath.Join(dirPath, "anotherDir")
if err := os.Mkdir(anotherDir, 0755); err != nil {
return fmt.Errorf("创建子目录 %s 失败: %v", anotherDir, err)
}
return nil
}
func worker(id int, ch <-chan int, wg *sync.WaitGroup, baseDir string) {
defer wg.Done()
// 每个worker有自己的随机数生成器
r := rand.New(rand.NewSource(time.Now().UnixNano() + int64(id)))
for a := range ch {
// 执行任务
if err := doSomething1(baseDir, a); err != nil {
fmt.Println(err)
continue
}
if err := doSomething2(baseDir, a); err != nil {
fmt.Println(err)
continue
}
// 随机休眠
randomSleep := time.Duration(r.Intn(10))
time.Sleep(randomSleep * time.Second)
fmt.Printf("Worker %d: 任务 %d 休眠 %d 秒\n", id, a, randomSleep)
}
}
func main() {
// 创建临时目录
baseDir, err := os.MkdirTemp(os.TempDir(), "test-")
if err != nil {
fmt.Println("创建临时目录失败:", err)
return
}
defer os.RemoveAll(baseDir) // 清理临时目录
fmt.Printf("工作目录: %s\n", baseDir)
// 创建任务通道
ch := make(chan int, folderCount)
var wg sync.WaitGroup
// 启动worker goroutines
wg.Add(xthreads)
for i := 0; i < xthreads; i++ {
go worker(i, ch, &wg, baseDir)
}
// 发送任务到通道
for i := 0; i < folderCount; i++ {
ch <- i
}
// 关闭通道,通知worker没有更多任务
close(ch)
// 等待所有worker完成
wg.Wait()
fmt.Println("\n所有任务完成!")
fmt.Println("生成的目录结构:")
// 验证生成的目录结构
for i := 0; i < folderCount; i++ {
dirPath := filepath.Join(baseDir, "testDir"+strconv.Itoa(i))
subDirPath := filepath.Join(dirPath, "anotherDir")
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
fmt.Printf(" ❌ 目录不存在: %s\n", dirPath)
} else if _, err := os.Stat(subDirPath); os.IsNotExist(err) {
fmt.Printf(" ❌ 子目录不存在: %s\n", subDirPath)
} else {
fmt.Printf(" ✓ testDir%d/anotherDir\n", i)
}
}
}
关键修改:
- 移除全局目录切换:使用
filepath.Join()构建完整路径,避免os.Chdir()导致的竞态条件 - 修复随机数生成:每个worker使用独立的随机数生成器,避免并发调用
rand.Seed()的问题 - 改进错误处理:函数返回错误而不是直接打印
- 添加worker ID:便于调试和跟踪
- 添加结果验证:最后检查所有目录是否正确创建
运行示例输出:
工作目录: /tmp/test-1234567890
doSomething1: 0
doSomething2: 0
Worker 0: 任务 0 休眠 3 秒
doSomething1: 1
doSomething2: 1
Worker 1: 任务 1 休眠 7 秒
doSomething1: 2
doSomething2: 2
Worker 2: 任务 2 休眠 2 秒
...
所有任务完成!
生成的目录结构:
✓ testDir0/anotherDir
✓ testDir1/anotherDir
✓ testDir2/anotherDir
...
✓ testDir49/anotherDir
这个版本确保了:
- 最多5个goroutine并发执行
- 目录结构正确创建
- 没有竞态条件
- 正确的错误处理

