编程 Go语言中的`sync`包,并发编程中的常用同步工具,包括互斥锁、读写锁、信号量

2024-11-18 23:14:17 +0800 CST views 573

概述

sync 包为 Go 语言中的并发编程提供了基础的同步机制。并发编程是一个非常广泛且复杂的话题,本文将重点介绍 sync 包中的一些常用工具及其使用方法,包括互斥锁、读写锁、信号量、WaitGroupOnce 等内容。

首先,简单介绍并发编程的背景。在单线程程序中,同一时刻只有一个线程可以访问数据,因此不需要额外的同步机制来保障数据的一致性。然而,在多线程环境下,多个线程可能同时访问相同的数据,这种情况下由于线程调度的不确定性,可能会导致数据不一致或其他难以预料的问题。

考虑以下代码示例,正常情况下应输出结果 3,但实际上可能输出 23,这是因为操作 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 提供了两个方法 LockUnlock,用于锁定和解锁。每个互斥锁只能被锁定一次,如果在已锁定的锁上执行 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 类似,但提供了 RLockRUnlock 方法来进行读锁定和解锁。详细使用可以参考 sync.RWMutex

信号量

sync.Cond 是信号量的实现,常用于生产者-消费者模型。在这种模型中,生产者完成一个任务后,会发送一个信号通知消费者,而消费者则会等待该信号。

NewCond

Cond 对象通过 NewCond 初始化,并绑定到一个 Locker(通常是 Mutex)。

type Cond struct {
	L Locker
}

func NewCond(l Locker) *Cond

Signal & Broadcast

SignalBroadcast 用于唤醒等待的线程。Signal 唤醒一个线程,而 Broadcast 唤醒所有等待的线程。

func (c *Cond) Signal()

func (c *Cond) Broadcast()

Wait

Wait 会阻塞当前线程,直到 SignalBroadcast 被调用时唤醒。

func (c *Cond) Wait()

WaitGroup

WaitGroup 用于等待一组并发操作完成。与信号量类似,但它可以等待多个任务。

Add

增加 WaitGroup 的计数,表示有多少个任务需要等待完成。

func (wg *WaitGroup) Add(delta int)

Done

每次调用 DoneWaitGroup 的计数减 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 确保某个函数只执行一次。OnceDo 方法接受一个无参函数作为参数,保证该函数仅执行一次。

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 包中常用的并发同步工具,理解这些工具可以帮助我们编写更安全、更高效的并发程序。

复制全文 生成海报 Go语言 并发编程 同步机制

推荐文章

Vue3中的Slots有哪些变化?
2024-11-18 16:34:49 +0800 CST
在 Vue 3 中如何创建和使用插件?
2024-11-18 13:42:12 +0800 CST
三种高效获取图标资源的平台
2024-11-18 18:18:19 +0800 CST
PyMySQL - Python中非常有用的库
2024-11-18 14:43:28 +0800 CST
JavaScript设计模式:适配器模式
2024-11-18 17:51:43 +0800 CST
js常用通用函数
2024-11-17 05:57:52 +0800 CST
用 Rust 构建一个 WebSocket 服务器
2024-11-19 10:08:22 +0800 CST
CentOS 镜像源配置
2024-11-18 11:28:06 +0800 CST
php获取当前域名
2024-11-18 00:12:48 +0800 CST
一键压缩图片代码
2024-11-19 00:41:25 +0800 CST
Nginx 性能优化有这篇就够了!
2024-11-19 01:57:41 +0800 CST
12个非常有用的JavaScript技巧
2024-11-19 05:36:14 +0800 CST
软件定制开发流程
2024-11-19 05:52:28 +0800 CST
55个常用的JavaScript代码段
2024-11-18 22:38:45 +0800 CST
deepcopy一个Go语言的深拷贝工具库
2024-11-18 18:17:40 +0800 CST
Vue3中如何进行异步组件的加载?
2024-11-17 04:29:53 +0800 CST
thinkphp分页扩展
2024-11-18 10:18:09 +0800 CST
Nginx 防盗链配置
2024-11-19 07:52:58 +0800 CST
linux设置开机自启动
2024-11-17 05:09:12 +0800 CST
程序员茄子在线接单