介绍Golang互斥锁

使用Go实现高度并行程序并不妨碍你实现具有竞争条件特性的应用。竞争条件会引起应用出现不期望的问题,且很难调试和修复。因此我们需要使用Go实现在安全方式下实现并行应用,并且不影响性能,这就是互斥锁要发挥的作用。

1. 互斥锁

互斥锁(Mutex)是一种机制,它阻止并发进程进入一个临界数据段,而该临界数据段已经被给定进程占用。

下面使用现实示例进行说明。我们有一个银行账户,应用系统提供对账户实现存款和取款功能。在单线程同步程序这很容易实现,只需要必要单元测试就可以有效地保障程序按照预期需求执行。但如果在多线程或协程场景下,我们的代码可能会遇到问题:

  1. 假设客户账号有1000元
  2. 客户往账号中存款500元
  3. 一个协程处理该事务,读账号获得余额1000,然后给该账号增加500
  4. 同时,另一个应用需要付700元按揭贷款
  5. 第二个协程在第一个程序增加500元存款之前读账号获得余额1000,然后给该账号扣除700元
  6. 客户第二天检查银行账号余额,发现仅剩300元。因为第二个协程并不知道第一个协程存款,重写了存款金额。

显然客户无法接受无缘无故的损失,这就是竞争条件下造成异常结果。

2. 示例

现在我们知道了问题缘由,让我们使用mutex修复该问题。为了使用互斥锁,需要导入sync包。

我们开始分析上面程序。 在deposit()和 withdraw()函数里,首先都通过mutex.Lock()获得mutex,每个函数都被阻塞直到获得锁,一旦获得锁,才可以进入竞争条件部分执行操作,读信息,接着更新余额。每个函数执行完相应操作通过mutex.Unlock()方法释放锁。执行上述示例输出结果:

提示:Go中能够安全地并发访问数组吗?当然不行,Go中并发读写访问都不安全,需要手动使用mutex保障。

3. 避免死锁

当使用互斥锁时,很多时候需要注意死锁。死锁是代码不能执行,每个线程或协程都被阻塞等待获得锁。

  • 确保调用Unlock()方法

如果正在开发的协程需要锁,其中有很多种方式终止,无论哪种方式终止都要确保协程调用Unlock()方法。如果调用Unlock()方法时有错误,程序可能会进入死锁,因为其他程序无法获得互斥锁。

  • 调用 Lock() 两次

在应用中使用互斥锁时要注意,Lock()方法会阻塞,需要确保在你开发的应用中不要对同一个锁调用Lock()方法两次,这会导致死锁。

执行程序会地得错误输出:

4. 总结

本文介绍了竞争条件下如何实现并行应用,并通过示例分析并行应用中造成非预期的问题以及如何通过互斥锁实现安全的并行程序。希望对你学习有点帮助。