概述
sync
包为 Go 语言中的并发编程提供了基础的同步机制。并发编程是一个非常广泛且复杂的话题,本文将重点介绍 sync
包中的一些常用工具及其使用方法,包括互斥锁、读写锁、信号量、WaitGroup
、Once
等内容。
首先,简单介绍并发编程的背景。在单线程程序中,同一时刻只有一个线程可以访问数据,因此不需要额外的同步机制来保障数据的一致性。然而,在多线程环境下,多个线程可能同时访问相同的数据,这种情况下由于线程调度的不确定性,可能会导致数据不一致或其他难以预料的问题。
考虑以下代码示例,正常情况下应输出结果 3
,但实际上可能输出 2
或 3
,这是因为操作 Add1
不是原子操作,线程调度可能在中途切换,导致两个线程对相同的值进行了并发操作,但结果却不如预期。
package main
import (
"fmt"
"time"
)
func Add1(data *int) {
tmp := *data
*data = tmp + 1
}
func main() {
data := 1
go Add1(&data)
go Add1(&data)
time.Sleep(10 * time.Millisecond)
fmt.Println(data)
}
并发编程的核心是在乱序执行的代码中创建小块的临界区,使这些代码能够线性执行,确保代码的执行结果符合预期。
互斥锁
sync.Mutex
是互斥锁的实现,它是同步机制中最经典的模型之一。Mutex
提供了两个方法 Lock
和 Unlock
,用于锁定和解锁。每个互斥锁只能被锁定一次,如果在已锁定的锁上执行 Lock
操作,该操作将阻塞,直到该锁被解锁为止。
Mutex
的声明如下:
type Mutex struct {
// 包含未导出的字段
}
func (m *Mutex) Lock()
func (m *Mutex) Unlock()
我们可以使用互斥锁来确保并发安全。为了确保在函数结束时解锁,通常使用 defer
来解锁:
package main
import (
"fmt"
"sync"
"time"
)
func Add1(data *int, mu *sync.Mutex) {
mu.Lock()
defer mu.Unlock()
tmp := *data
*data = tmp + 1
}
func main() {
var mu sync.Mutex
data := 1
go Add1(&data, &mu)
go Add1(&data, &mu)
time.Sleep(10 * time.Millisecond)
fmt.Println(data)
}
在这个例子中,Mutex
确保了 Add1
函数在并发执行时不会导致数据的不一致。
读写锁
对于只读操作,使用互斥锁会带来不必要的性能开销。sync.RWMutex
提供了读写锁,它允许并发读取,但写操作会独占锁。
RWMutex
的使用与 Mutex
类似,但提供了 RLock
和 RUnlock
方法来进行读锁定和解锁。详细使用可以参考 sync.RWMutex。
信号量
sync.Cond
是信号量的实现,常用于生产者-消费者模型。在这种模型中,生产者完成一个任务后,会发送一个信号通知消费者,而消费者则会等待该信号。
NewCond
Cond
对象通过 NewCond
初始化,并绑定到一个 Locker
(通常是 Mutex
)。
type Cond struct {
L Locker
}
func NewCond(l Locker) *Cond
Signal
& Broadcast
Signal
和 Broadcast
用于唤醒等待的线程。Signal
唤醒一个线程,而 Broadcast
唤醒所有等待的线程。
func (c *Cond) Signal()
func (c *Cond) Broadcast()
Wait
Wait
会阻塞当前线程,直到 Signal
或 Broadcast
被调用时唤醒。
func (c *Cond) Wait()
WaitGroup
WaitGroup
用于等待一组并发操作完成。与信号量类似,但它可以等待多个任务。
Add
增加 WaitGroup
的计数,表示有多少个任务需要等待完成。
func (wg *WaitGroup) Add(delta int)
Done
每次调用 Done
,WaitGroup
的计数减 1。
func (wg *WaitGroup) Done()
Wait
阻塞等待,直到 WaitGroup
的计数为 0。
func (wg *WaitGroup) Wait()
示例
下面的示例创建了一个 WaitGroup
,并发执行了三个任务,最后等待它们全部完成:
package main
import (
"fmt"
"sync"
)
func handle(wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("Done Once")
}
func main() {
var wg sync.WaitGroup
wg.Add(3)
for i := 0; i < 3; i++ {
go handle(&wg)
}
wg.Wait()
}
Once
sync.Once
确保某个函数只执行一次。Once
的 Do
方法接受一个无参函数作为参数,保证该函数仅执行一次。
func (o *Once) Do(f func())
示例代码:
package main
import (
"fmt"
"sync"
)
func main() {
var once sync.Once
for i := 0; i < 10; i++ {
once.Do(func() { fmt.Println("Do Once") })
}
}
输出结果只会打印一次 "Do Once"。
以上内容涵盖了 sync
包中常用的并发同步工具,理解这些工具可以帮助我们编写更安全、更高效的并发程序。