当前位置: 首页 > 工具软件 > Fish Sync > 使用案例 >

go sync 库 学习

宋丰
2023-12-01

Desc:Go sync 包的使用方法,sync.Mutex,sync.RMutex,sync.Once,sync.Cond,sync.Waitgroup
尽管 Golang 推荐通过 channel 进行通信和同步,但在实际开发中 sync 包用得也非常的多。另外 sync 下还有一个 atomic 包,提供了一些底层的原子操作(这里不做介绍)。本篇文章主要介绍该包下的锁的一些概念及使用方法。

整个包都围绕这 Locker 进行,这是一个 interface:

type Locker interface {
        Lock()
        Unlock()
}

只有两个方法,Lock() 和 Unlock()

另外该包下的对象,在使用过之后,千万不要复制。

有许多同学不理解锁的概念,下面会一一介绍到:

为什么需要锁?

并发的情况下,多个线程或协程同时去修改一个变量,可能会出现如下情况:

package main
 
import (
    "fmt"
    "sync"
    "time"
)
 
func main() {
    var a = 0
 
    // 启动 100 个协程,需要足够大
    // var lock sync.Mutex
    for i := 0; i < 100; i++ {
        go func(idx int) {
            // lock.Lock()
            // defer lock.Unlock()
            a += 1
            fmt.Printf("goroutine %d, a=%d\n", idx, a)
        }(i)
    }
 
    // 等待 1s 结束主程序
    // 确保所有协程执行完
    time.Sleep(time.Second)
}
 

观察打印结果,是否出现 a 的值是相同的情况(未出现则重试或调大协程数),答案:是的。

显然这不是我们想要的结果。出现这种情况的原因是,协程依次执行:从寄存器读取 a 的值 -> 然后做加法运算 -> 最后写会寄存器。试想,此时一个协程取出 a 的值 3,正在做加法运算(还未写回寄存器)。同时另一个协程此时去取,取出了同样的 a 的值 3。最终导致的结果是,两个协程产出的结果相同,a 相当于只增加了 1。

所以,锁的概念就是,我正在处理 a(锁定),你们谁都别和我抢,等我处理完了(解锁),你们再处理。这样就实现了,同时处理 a 的协程只有一个,就实现了同步。

把上面代码里的注释取消掉再试下。

每次确定都是100,这个很像Java和数据库的悲观锁

什么是互斥锁 Mutex?

什么是互斥锁?它是锁的一种具体实现,有两个方法:

  1. func (m *Mutex) Lock()

  2. func (m *Mutex) Unlock()

在首次使用后不要复制该互斥锁。对一个未锁定的互斥锁解锁将会产生运行时错误。

一个互斥锁只能同时被一个 goroutine 锁定,其它 goroutine 将阻塞直到互斥锁被解锁(重新争抢对互斥锁的锁定)。

使用例子:

package main
 
import (
    "fmt"
    "sync"
    "time"
)
 
func main() {
    ch := make(chan struct{}, 2)
 
    var l sync.Mutex
    go func() {
        l.Lock()
        defer l.Unlock()
        fmt.Println("goroutine1: 我会锁定大概 2s")
        time.Sleep(time.Second * 2)
        fmt.Println("goroutine1: 我解锁了,你们去抢吧")
        ch <- struct{}{}
    }()
 
    go func() {
        fmt.Println("groutine2: 等待解锁")
        l.Lock()
        defer l.Unlock()
        fmt.Println("goroutine2: 哈哈,我锁定了")
        ch <- struct{}{}
    }()
 
    // 等待 goroutine 执行结束
   	for i := 0; i < 2; i++ {
		s := <-ch
		fmt.Println(s)
	}
}

WaitGroup 例子

WaitGroup 用于等待一组 goroutine 结束,用法很简单。它有三个方法:

func (wg *WaitGroup) Add(delta int)  //Add(n) 把计数器设置为n ,
func (wg *WaitGroup) Done()          //Done() 每次把计数器-1 
func (wg *WaitGroup) Wait()          //wait() 会阻塞代码的运行,直到计数器地值减为0
package main

import (
	"sync"
	"fmt"
)

func main() {
	wg := sync.WaitGroup{}
	wg.Add(100)
	for i := 0; i < 100; i++ {
		go func(i int) {
			fmt.Println(i)
			wg.Done()
		}(i)
	}
	wg.Wait()
}

sync.Once的用法:

sync.Once.Do(f func())是一个能保证once只执行一次,无论你是否更换once.Do(xx)这里的方法,这个sync.Once块只会执行一次。

package main

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

var once sync.Once

func main() {

	for i, v := range make([]string, 10) {
		once.Do(onces)
		fmt.Println("count:", v, "---", i)
	}
	for i := 0; i < 10; i++ {

		go func() {
			once.Do(onced)
			fmt.Println("213")
		}()
	}
	time.Sleep(4000)
}
func onces() {
	fmt.Println("onces")
}
func onced() {
	fmt.Println("onced")
}

 

 

 

 

 

 

 

 

 类似资料: