golang同步之sync包

本文介绍了Go语言中的并发机制及同步工具,包括互斥锁Mutex、条件变量Cond、等待组WaitGroup、单例模式Once和对象池Pool等。通过具体实例展示了如何使用这些工具确保并发程序的一致性和效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

golang中实现并发非常简单,只需在需要并发的函数前面添加关键字"go",但是如何处理go并发机制中不同goroutine之间的同步与通信,golang 中提供了sync包来解决相关的问题,当然还有其他的方式比如channel,原子操作atomic等等,这里先介绍sync包的用法。

sync 包提供了互斥锁这类的基本的同步原语.除 Once 和 WaitGroup 之外的类型大多用于底层库的例程。更高级的同步操作通过信道与通信进行。

type Cond

    func NewCond(l Locker) *Cond

    func (c *Cond) Broadcast()
    func (c *Cond) Signal()
    func (c *Cond) Wait()
type Locker
type Mutex
    func (m *Mutex) Lock()
    func (m *Mutex) Unlock()
type Once
    func (o *Once) Do(f func())
type Pool
    func (p *Pool) Get() interface{}
    func (p *Pool) Put(x interface{})
type RWMutex
    func (rw *RWMutex) Lock() 写锁
    func (rw *RWMutex) RLock()                       读锁
    func (rw *RWMutex) RLocker() Locker
    func (rw *RWMutex) RUnlock()
    func (rw *RWMutex) Unlock()
type WaitGroup
    func (wg *WaitGroup) Add(delta int)
    func (wg *WaitGroup) Done()
    func (wg *WaitGroup) Wait()
    golang中的同步是通过sync.WaitGroup来实现的.WaitGroup的功能:它实现了一个类似队列的结构,可以一直向队列中添加任务,当任务完成后便从队列中删除,如果队列中的任务没有完全完成,可以通过Wait()函数来出发阻塞,防止程序继续进行,直到所有的队列任务都完成为止.
WaitGroup总共有三个方法:
Add(delta int), Done(), Wait()。Add:添加或者减少等待goroutine的数量
Done:相当于Add(-1)
Wait:执行阻塞,直到所有的WaitGroup数量变成0
package main

import (
	"fmt"
	"sync"
)

var waitgroup sync.WaitGroup

func function(i int) {
	fmt.Println(i)
	waitgroup.Done() //任务完成,将任务队列中的任务数量-1,其实.Done就是.Add(-1)
}

func main() {
	for i := 0; i < 10; i++ {
	    //每创建一个goroutine,就把任务队列中任务的数量+1
		waitgroup.Add(1) 
		go function(i)
	}
	//这里会发生阻塞,直到队列中所有的任务结束就会解除阻塞
	waitgroup.Wait() 
}
      程序中需要并发,需要创建多个goroutine,并且一定要等这些并发全部完成后才继续接下来的程序执行.WaitGroup的特点是Wait()可以用来阻塞直到队列中的所有任务都完成时才解除阻塞,而不需要sleep一个固定的时间来等待
    接下来看cond用法,很简单一个goroutine等待另外的goroutine发送通知唤醒。
package main

import (
	"fmt"
	"sync"
	"time"
)
var locker = new(sync.Mutex)
var cond = sync.NewCond(locker)
var waitgroup sync.WaitGroup

func test(x int) {
	cond.L.Lock()
	//等待通知,阻塞在此
	cond.Wait()
	fmt.Println(x)
	time.Sleep(time.Second * 1)
	defer func() {
		cond.L.Unlock()//释放锁
		waitgroup.Done()
	}()
}

func main() {
	for i := 0; i < 10; i++ {
		go test(i)
		waitgroup.Add(1);
	}
	fmt.Println("start all")
	time.Sleep(time.Second * 1)
	// 下发一个通知给已经获取锁的goroutine
	cond.Signal()
	time.Sleep(time.Second * 1)
	// 下发一个通知给已经获取锁的goroutine
	cond.Signal()
	time.Sleep(time.Second * 1)
	//下发广播给所有等待的goroutine
	fmt.Println("start Broadcast")
	cond.Broadcast()
	waitgroup.Wait()
}
    然后看Once,它可以保证代码段植段只被执行一次,可以用来实现单例。
   
package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	var once sync.Once
	onceBody := func() {
		time.Sleep(3e9)
		fmt.Println("Only once")
	}
	done := make(chan bool)
	for i := 0; i < 10; i++ {
		j := i
		go func(int) {
			once.Do(onceBody)
			fmt.Println(j)
			done <- true
		}(j)
	}
	<-done
	time.Sleep(3e9)
}
    用once可以保证上面的oncebody被执行一次,即使被多次调用,内部用一个atmoic int字段标示是否被执行过,和一个锁来实现,具体的可以看go的源码,syc目录下的once.go
    然后说道pool,说白了就是一个对象池,这个类设计的目的是用来保存和复用临时对象,以减少内存分配,降低CG压力。
    Get返回Pool中的任意一个对象。如果Pool为空,则调用New返回一个新创建的对象。如果没有设置New,则返回nil。还有一个重要的特性是,放进Pool中的对象,会在说不准什么时候被回收掉。所以如果事先Put进去100个对象,下次Get的时候发现Pool是空也是有可能的。不过这个特性的一个好处就在于不用担心Pool会一直增长,因为Go已经帮你在Pool中做了回收机制。这个清理过程是在每次垃圾回收之前做的。垃圾回收是固定两分钟触发一次,而且每次清理会将Pool中的所有对象都清理掉!
   
func main(){
	// 建立对象
	var pipe = &sync.Pool{New:func()interface{}{return "Hello,BeiJing"}}
	// 准备放入的字符串
	val := "Hello,World!"
	// 放入
	pipe.Put(val)
	// 取出
	log.Println(pipe.Get())
	// 再取就没有了,会自动调用NEW
	log.Println(pipe.Get())
} 
   最后 RWMutex读写锁,RWMutex有两种锁写锁和读锁,用法也有不同,首先读锁可以同时加多个,但是写锁就不行当你试图加第二个写锁时就回导致当前的goroutine或者线程阻塞,但是这里的读锁就不会,那他有什么作用呢。
   当有读锁,试图加写锁会阻塞,当有写锁,试图加读锁时会阻塞,当有读锁,试图加读锁时不会阻塞,这样有什么好处呢,当我们有一种数据读操作远远多于写操作时,当我们读时,如果加mutex或者写锁,会大大影响其他线程,因为我们大多数是读操作,因此如果我们加读锁,就不会影响其他线程的读操作,同时有线程写时也能保证数据的同步。最后一点很重要,不论是读锁还是写锁lock和unlock时一一对应的,unlock前一 定要有lock,就像c++的new和delete,一定要注意。
   下 面看两个例子:来源:点击打开链接
    随便读:注意此时此时不能写。
 
package main

import (
    "sync"
    "time"
)

var m *sync.RWMutex

func main() {
    m = new(sync.RWMutex)
    
    // 多个同时读
    go read(1)
    go read(2)

    time.Sleep(2*time.Second)
}

func read(i int) {
    println(i,"read start")

    m.RLock()
    println(i,"reading")
    time.Sleep(1*time.Second)
    m.RUnlock()    

    println(i,"read over")
}
   写的时候不可读也不可写
   
package main

import (
    "sync"
    "time"
)

var m *sync.RWMutex

func main() {
    m = new(sync.RWMutex)
    
    // 写的时候啥也不能干
    go write(1)
    go read(2)
    go write(3)

    time.Sleep(2*time.Second)
}

func read(i int) {
    println(i,"read start")

    m.RLock()
    println(i,"reading")
    time.Sleep(1*time.Second)
    m.RUnlock()    

    println(i,"read over")
}

func write(i int) {
    println(i,"write start")

    m.Lock()
    println(i,"writing")
    time.Sleep(1*time.Second)
    m.Unlock()

    println(i,"write over")
}
### Golang 中 `sync` 的使用方法 #### 1. 基本概念 Go 的标准库中,`sync` 提供了一些基础工具来支持多线程环境下的同步操作。这些工具主要用于实现并发控制和数据共享的安全机制[^1]。 #### 2. 主要功能组件 以下是 `sync` 的一些核心功能及其用途: - **Mutex (互斥锁)** Mutex 是一种简单的锁定原语,用于保护临界区代码段不被多个 Goroutine 同时访问。通过调用 `Lock()` 和 `Unlock()` 方法可以分别加锁和解锁。 ```go var mu sync.Mutex func incrementCounter() { mu.Lock() defer mu.Unlock() // 修改共享变量的操作 } ``` - **RWMutex (读写锁)** RWMutex 提供了一种更灵活的方式,在允许多个读者的同时只允许单个写者进入。这适合于频繁读取而较少修改的数据结构场景。 ```go var rwmu sync.RWMutex func readData() { rwmu.RLock() defer rwmu.RUnlock() // 只读操作 } func writeData() { rwmu.Lock() defer rwmu.Unlock() // 数据更新操作 } ``` - **WaitGroup (等待组)** WaitGroup 能够让一个 Goroutine 等待其他一组 Goroutine 完成后再继续执行。它常用来协调主流程与其他子任务之间的关系。 ```go var wg sync.WaitGroup func doWork(id int) { defer wg.Done() fmt.Printf("Worker %d starting\n", id) time.Sleep(time.Second) fmt.Printf("Worker %d done\n", id) } func main() { const numWorkers = 5 wg.Add(numWorkers) for i := 1; i <= numWorkers; i++ { go doWork(i) } wg.Wait() fmt.Println("All workers have finished.") } ``` - **Once (一次性初始化)** Once 类型确保某个动作在整个程序生命周期内仅被执行一次,即使有多个 Goroutine 尝试触发该动作也无妨。 ```go var once sync.Once func initResource() { once.Do(func() { fmt.Println("Initializing resource...") // 初始化逻辑 }) } ``` #### 3. 应用场景与注意事项 虽然 `sync` 提供了丰富的同步手段,但在实际项目开发过程中需要注意以下几点: - 避免死锁情况的发生; - 控制好锁粒度大小以免影响性能; - 对于高阶抽象层次上的协作问题建议优先考虑 Channel 方式而非直接依赖原始同步构件。 #### 4. 学习资料推荐 对于希望深入理解并掌握 Go 并发特性的开发者来说,《Concurrency in Go》是一本非常值得阅读的好书。另外也可以参考官方文档以及社区分享的学习笔记等内容进一步巩固知识点[^2]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值