zl程序教程

您现在的位置是:首页 >  其他

当前栏目

Go错误集锦 | 字符串格式化竟然能引起死锁

2023-02-26 09:48:39 时间

今天跟大家分享一个关于格式化字符串时造成的死锁现象及对应的解决方案。以便大家在今后的研发中可以避免类似情况的出现。

假设我们有以下Customer结构体,该结构体的方法可以被并发访问。我们使用sync.RWMutex来保护并发读写。然后我们实现一个UpdateAge方法来更新Customer的age值,同时检查该age如果是负数,则使用fmt.Errorf返回一个格式化的字符串错误。同时,Customer还实现了Stringer接口的String方法。代码如下:

type Customer struct {
    mutex sync.RWMutex 
    id string
    age int
}

func (c *Customer) UpdateAge(age int) error {
    c.mutex.Lock() 
    defer c.mutex.Unlock()    
    
    if age < 0 { 
        return fmt.Errorf("age should be positive for customer %s", c)
    }

    c.age = age    
    return nil
}
   
func (c *Customer) String() string { 
    c.mutex.RLock() 
    defer c.mutex.RUnlock()     
   
    return fmt.Sprintf("id %s, age %d", c.id, c.age)
}

上述代码有什么问题吗?

问题在于当我们调用UpdateAge方法时,有可能会产生死锁。因为若age是负数,那么会返回一个错误,又因为在错误中使用了%s对结构体实例进行字符串输出,所以会调用Customer的String方法。但是在UpdateAge中已经获取了互斥锁,还没来得及释放,在String中的c.mutex.RLock()就获取不到锁,并阻塞在这里,这样就造成了死锁。

这种问题应该怎么解决?

一种方法就是改进互斥锁的限制区域。实际上,在UpdateAge中,我们是先加锁,然后再判断age是否是负数。我们可以将二者交换,先判断age是否为负数,然后再进行加锁。如下:

func (c *Customer) UpdateAge(age int) error {
    if age < 0 {
        return fmt.Errorf("age should be positive for customer %s", c)
    }
    
    c.mutex.Lock()    
    defer c.mutex.Unlock()
    
    c.age = age    
    return nil
}

这样,当age是负数并返回格式化的字符串时,就不会产生锁竞争从而导致死锁的问题。以上案例,希望能够帮助大家在实际的研发过程中避免再踩相同的坑。

欢迎关注「Go学堂」,让知识活起来