Go中的同步与锁

时间:2022-05-06
本文章向大家介绍Go中的同步与锁,主要内容包括Mutex 互斥锁、RWMutex 读写锁、Once、WaitGroup、参考、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。
最近学习了Go语言中同步包中的互斥锁、读写锁、Once、waitGroup。在并发程序开发的过程中,这两种锁是非常重要的,包括对共享资源进行访问控制的时候。sync是Go语言中的标准库。

Mutex 互斥锁

  • 互斥锁是传统并发程序对共享资源进行访问控制的主要手段。是sync包中的Mutex结构体。
    • type Mutex struct {}
  • 该结构体包括了两个方法,可以说是非常简单使用的
    • func (m *Mutex) Lock() {}
    • func (m *Mutex) Unlock() {}
  • 我们通过一个简单的例子来说明他的用法:
package main

import (

	"sync"

	"fmt"

)

type safeCounter struct {

	number int

	sync.Mutex

}

func (sc *safeCounter) Increment() {

	sc.Lock()

	sc.number++

	sc.Unlock()

}

func (sc *safeCounter) Decrement() {

	sc.Lock()

	sc.number--

	sc.Unlock()

}

func (sc *safeCounter) getNumber() int {

	sc.Lock()

	number := sc.number

	sc.Unlock()

	return number

}

func main() {

	sc := new(safeCounter)

	for i := 0; i < 100; i++ {

		go sc.Increment()

		go sc.Decrement()

	}

	fmt.Println(sc.getNumber())

}
  • 上述例子通过互斥锁来保证number整型变量在使用goroutine的情况下,持续稳定地输出0的结果。当然你可以试一下,去掉锁的保护,是否还可以稳定输出0?

RWMutex 读写锁

  • 读写锁是针对读写操作的互斥锁。遵循两个原则即可
    • 可以随便地读,并且支持多个goroutine同时读取
    • 写的时候,既不能读也不能写。
  • RWMutex 提供了四个方法
    • func (*RWMutex) Lock
    • func (*RWMutex) Unlock
    • func (*RWMutex) RLock
    • func (*RWMutex) RUnlock
  • 通过读写map的一个例子来说明RWMutex的使用方法:
package main

import (

	"fmt"

	"math/rand"

	"sync"

	"time"

)

type MapCounter struct {

	m map[int]int

	sync.RWMutex

}

func (mapCounter *MapCounter) Reader(n int) {

	for {

		mapCounter.RLock()

		v := mapCounter.m[rand.Intn(n)]

		mapCounter.RUnlock()

		fmt.Println(v)

		time.Sleep(1 * time.Second)

	}

}

func (mapCounter *MapCounter) Writer(n int) {

	for i := 0; i < n; i++ {

		mapCounter.Lock()

		mapCounter.m[i] = i * 10

		mapCounter.Unlock()

		time.Sleep(1 * time.Second)

	}

}

func main() {

	mc := MapCounter{m: make(map[int]int)}

	go mc.Writer(10)

	go mc.Reader(10)

	go mc.Reader(10)

	time.Sleep(15 * time.Second)

}

Once

  • Once 顾名思义,就是只执行一次 。通过一个简单的例子就可以说明:
package main

import (

	"fmt"

	"sync"

)

func main() {

	once := sync.Once{}

	done := make(chan bool)

	for i := 0; i < 10; i++ {

		go func() {

			once.Do(sayHello)

			done <- true

		}()

	}

	for i := 0; i < 10; i++ {

		fmt.Println(<-done)

	}

}

func sayHello() {

	fmt.Println("Hello")

}
  • 以上的例子只输出了一个“Hello” ,其余的都是true。

WaitGroup

  • WaitGroup 是可以一个goroutine集合,当集合里的goroutine执行完毕后,这个WaitGroup就会自动结束。你可以定义这个WaitGroup的大小。他里面只有三个方法:
    • func (wg *WaitGroup) Add(delta int) {} //定义大小
    • func (wg *WaitGroup) Done() {}
    • func (wg *WaitGroup) Wait() {}
  • 具体用法,通过下面的小例子说明。
package main

import (

	"fmt"

	"sync"

)

func main() {

	wg := sync.WaitGroup{}

	for i := 0; i <= 5; i++ {

		wg.Add(1)

		go func(i int) {

			defer wg.Done()

			fmt.Println("Work done for ", i)

		}(i)

	}

	wg.Wait()

}
  • 建议大家可以把上面的代码都跑一遍,方便自己理解代码的含义。结合官方文档来理解会更加容易。

参考