Golang中image/draw是否线程安全?
Golang中image/draw是否线程安全?
// CompositeImage numSizeRatio is num + '#' size_ratio
func CompositeImage(numSizeRatio string, imageUrls []string) ([]byte, error) {
compositeImageParameter := CompositeImageParameterMap[numSizeRatio]
basemap := image.NewRGBA(image.Rect(0, 0, compositeImageParameter.BaseImageWidth, compositeImageParameter.BaseImageHeight))
var wg sync.WaitGroup
for i := 0; i < len(imageUrls); i++ {
wg.Add(1)
go func(i int) {
defer func() {
if err := recover(); err != nil {
log.Errorf("panic stack: %s", string(debug.Stack()))
log.Error("CompositeImage panic:", err)
}
wg.Done()
}()
imageBytes, err := util.GetRaw(imageUrls[i])
if err != nil {
log.Errorf("GetRaw image %d error: %v", i, err)
return
}
buffer := bytes.NewBuffer(imageBytes)
imageDecode, err := webp.Decode(buffer)
if err != nil {
imageDecode, _, err = image.Decode(bytes.NewBuffer(imageBytes))
if err != nil {
log.Errorf("Decode image %d error: %v", i, err)
return
}
}
draw.Draw(basemap, image.Rect(compositeImageParameter.Offset[i][0], compositeImageParameter.Offset[i][1], compositeImageParameter.Offset[i][2], compositeImageParameter.Offset[i][3]),
imageDecode, imageDecode.Bounds().Min, draw.Over)
}(i)
}
wg.Wait()
outputImageBytes := bytes.NewBuffer([]byte{})
if err := png.Encode(outputImageBytes, basemap); err != nil {
return nil, errors.Wrap(err, "png Encode error")
}
return outputImageBytes.Bytes(), nil
}
我使用了 go test -race 进行测试,没有发现错误。
更多关于Golang中image/draw是否线程安全?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
我不会假设它是安全的,除非文档明确指出这一点,或者你已经检查了源代码以确认它具备适当的同步机制。
竞态检测器只能发现运行时发生的竞态条件
你的 goroutine 中包含许多其他耗时的操作,因此 Draw 没有被同时调用可能只是运气好。考虑到你涉及 I/O 和内存分配操作,无论如何你都无法获得太多的并行性。你的多个 goroutine 主要有助于实现异步 I/O。因此,为了安全起见,请将你的 Draw 函数放在另一个独立的 goroutine 中,并通过一个 chan 向其发送数据(前三条 Go 谚语与此相关:https://go-proverbs.github.io/),或者使用互斥锁来保护它。
更多关于Golang中image/draw是否线程安全?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
image/draw 包中的 Draw 函数不是线程安全的。在你的代码中,多个 goroutine 并发地对同一个 basemap 进行绘制操作,这会导致数据竞争。虽然 go test -race 可能没有立即报告错误,但并发写入同一图像内存区域是不安全的。
下面是修复后的线程安全版本:
func CompositeImage(numSizeRatio string, imageUrls []string) ([]byte, error) {
compositeImageParameter := CompositeImageParameterMap[numSizeRatio]
basemap := image.NewRGBA(image.Rect(0, 0, compositeImageParameter.BaseImageWidth, compositeImageParameter.BaseImageHeight))
// 使用互斥锁保护对 basemap 的并发访问
var mu sync.Mutex
var wg sync.WaitGroup
errCh := make(chan error, len(imageUrls))
for i := 0; i < len(imageUrls); i++ {
wg.Add(1)
go func(idx int) {
defer wg.Done()
imageBytes, err := util.GetRaw(imageUrls[idx])
if err != nil {
errCh <- fmt.Errorf("GetRaw image %d error: %v", idx, err)
return
}
buffer := bytes.NewBuffer(imageBytes)
imageDecode, err := webp.Decode(buffer)
if err != nil {
imageDecode, _, err = image.Decode(bytes.NewBuffer(imageBytes))
if err != nil {
errCh <- fmt.Errorf("Decode image %d error: %v", idx, err)
return
}
}
// 加锁执行绘制操作
mu.Lock()
draw.Draw(basemap,
image.Rect(
compositeImageParameter.Offset[idx][0],
compositeImageParameter.Offset[idx][1],
compositeImageParameter.Offset[idx][2],
compositeImageParameter.Offset[idx][3],
),
imageDecode,
imageDecode.Bounds().Min,
draw.Over,
)
mu.Unlock()
}(i)
}
wg.Wait()
close(errCh)
// 检查是否有错误发生
for err := range errCh {
if err != nil {
log.Error(err)
}
}
outputImageBytes := bytes.NewBuffer([]byte{})
if err := png.Encode(outputImageBytes, basemap); err != nil {
return nil, fmt.Errorf("png Encode error: %w", err)
}
return outputImageBytes.Bytes(), nil
}
或者,如果每个 goroutine 绘制的是图像的不同区域(没有重叠),可以使用更细粒度的锁:
func CompositeImage(numSizeRatio string, imageUrls []string) ([]byte, error) {
compositeImageParameter := CompositeImageParameterMap[numSizeRatio]
basemap := image.NewRGBA(image.Rect(0, 0, compositeImageParameter.BaseImageWidth, compositeImageParameter.BaseImageHeight))
// 为每个绘制区域创建独立的锁
locks := make([]sync.Mutex, len(imageUrls))
var wg sync.WaitGroup
for i := 0; i < len(imageUrls); i++ {
wg.Add(1)
go func(idx int) {
defer wg.Done()
imageBytes, err := util.GetRaw(imageUrls[idx])
if err != nil {
log.Errorf("GetRaw image %d error: %v", idx, err)
return
}
buffer := bytes.NewBuffer(imageBytes)
imageDecode, err := webp.Decode(buffer)
if err != nil {
imageDecode, _, err = image.Decode(bytes.NewBuffer(imageBytes))
if err != nil {
log.Errorf("Decode image %d error: %v", idx, err)
return
}
}
// 只锁定当前绘制的区域
locks[idx].Lock()
draw.Draw(basemap,
image.Rect(
compositeImageParameter.Offset[idx][0],
compositeImageParameter.Offset[idx][1],
compositeImageParameter.Offset[idx][2],
compositeImageParameter.Offset[idx][3],
),
imageDecode,
imageDecode.Bounds().Min,
draw.Over,
)
locks[idx].Unlock()
}(i)
}
wg.Wait()
outputImageBytes := bytes.NewBuffer([]byte{})
if err := png.Encode(outputImageBytes, basemap); err != nil {
return nil, fmt.Errorf("png Encode error: %w", err)
}
return outputImageBytes.Bytes(), nil
}
关键点:image/draw.Draw() 会修改目标图像的像素数据,当多个 goroutine 同时操作同一个图像时,必须使用同步机制来保护这些操作。

