sync包

sync包包含了对低级别内存访问同步最有用的并发原语。

1. WaitGroup

sync.WiteGroup的应用场景是等待一组并发操作完成。sync.WaitGroup拥有一个内部计数器。当计数器等于0时,Wait()方法会立即返回。否则它将阻塞执行Wait()方法的goroutine直到计数器等于0时为止。

要增加计数器,我们必须使用Add(int)方法。要减少它,我们可以使用Done()

我们可以将sync.WaitGroup视为一个并发-安全的计数器:调用通过传入的整数执行Add方法增加计数器的增量,并调用Done方法对计数器进行递减。Wait阻塞,直到计数器为零。

func main() {
	var wg sync.WaitGroup

	wg.Add(1)  // 表示一个Goroutine开始了
	go func() {
		defer wg.Done()
		fmt.Println("第一个Goroutine执行完成")
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		fmt.Println("第二个Goroutine执行完成")
	}()

	wg.Wait()
	fmt.Println("所有Goroutine执行完成")

}

执行结果为:

第二个Goroutine执行完成
第一个Goroutine执行完成
所有Goroutine执行完成

2. Mutex

Mutex是互斥的意思,是保护程序中需要独占访问共享资源的区域的一种方式,我们可以通过使用Mutex对内存进行保护来协调对内存的访问。

package main

import (
	"sync"
	"fmt"
)

var count int

func main() {
	var lock sync.Mutex
	increment := func() {
		lock.Lock()         // 请求对全局变量的独占,使用互斥锁来解决
		defer lock.Unlock() // 完成对全局变量的独占,释放互斥锁
		count ++
		fmt.Println("Increment: ", count)
	}

	decrement := func() {
		lock.Lock()
		defer lock.Unlock()
		count --
		fmt.Println("Decrement: ", count)
	}

	var wg sync.WaitGroup
	for i := 0; i <= 5; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			increment()
		}()
	}

	for i := 0; i <= 5; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			decrement()
		}()
	}

	wg.Wait()
	fmt.Println("全部Goroutine都完成")
}

输出结果为:

Increment:  1
Decrement:  0
Increment:  1
Increment:  2
Increment:  3
Decrement:  2
Decrement:  1
Decrement:  0
Decrement:  -1
Decrement:  -2
Increment:  -1
Increment:  0
全部Goroutine都完成

我们经常在defer语句中调用Unlock,这是很常见的习惯,它可以确保即使出现了panic,调用也总是执行。如果不这样做,程序可能会陷入死锁。

3. RWMutex

读写互斥锁在概念上和互斥是一样的:它保护着对内存的访问。读写锁适合于对数据结构的读次数比写次数多得多的情况。 因为, 读模式锁定时可以共享, 以写模式锁住时意味着独占。

Go语言中,读写锁由结构体类型sync.RWMutex代表。在此类型的方法集合中包含了两对方法,即:

func (*RWMutex) Lock
func (*RWMutex) Unlock

func (*RWMutex) RLock
func (*RWMutex) RUnlock

前一对方法的名称和签名与互斥锁的那两个方法完全一致。它们分别代表了对写操作的锁定和解锁。以下简称它们为写锁定和写解锁。而后一对方法则分别表示了对读操作的锁定和解锁。以下简称它们为读锁定和读解锁。

对已被写锁定的读写锁进行写锁定,会造成当前Goroutine的阻塞,直到该读写锁被写解锁。当然,如果有多个Goroutine因此而被阻塞,那么当对应的写解锁被进行之时只会使其中一个Goroutine的运行被恢复。类似的,对一个已被写锁定的读写锁进行读锁定,也会阻塞相应的Goroutine。但不同的是,一旦该读写锁被写解锁,那么所有因欲进行读锁定而被阻塞的Goroutine的运行都会被恢复。另一方面,如果在进行过程中发现当前的读写锁已被读锁定,那么这个写锁定操作将会等待直至所有施加于该读写锁之上的读锁定都被清除。同样的,在有多个写锁定操作为此而等待的情况下,相应的读锁定的全部清除只能让其中的某一个写锁定操作获得进行的机会。

package main

import (
	"sync"
	"math/rand"
	"time"
	"fmt"
)

var count int

var mutex sync.RWMutex

func write(n int) {
	rand.Seed(time.Now().UnixNano()) // 保证每次的结果都是随机的

	mutex.Lock()
	fmt.Printf("写goroutine %d 正在写数据\n", n)
	num := rand.Intn(500)
	count = num
	fmt.Printf("写goroutine %d 写数据结束,写入新值%d\n", n, num)
	mutex.Unlock()

}

func read(n int) {
	mutex.RLock()
	fmt.Printf("读goroutine %d 正在读数据\n", n)
	num := count
	fmt.Printf("读goroutine %d 读取数据结束,读到数据%d\n", n, num)
	mutex.RUnlock()
}

func main() {
	for i := 0; i < 10; i++ {
		go read(i + 1)
	}

	for i := 0; i < 10; i++ {
		go write(i + 1)
	}
	time.Sleep(time.Second * 5)
}

运行结果为:

读goroutine 1 正在读数据
读goroutine 1 读取数据结束,读到数据0
读goroutine 2 正在读数据
读goroutine 2 读取数据结束,读到数据0
读goroutine 6 正在读数据
读goroutine 6 读取数据结束,读到数据0
写goroutine 10 正在写数据
写goroutine 10 写数据结束,写入新值358
读goroutine 10 正在读数据
读goroutine 5 正在读数据
读goroutine 5 读取数据结束,读到数据358
读goroutine 7 正在读数据
读goroutine 7 读取数据结束,读到数据358
读goroutine 9 正在读数据
读goroutine 3 正在读数据
读goroutine 3 读取数据结束,读到数据358
读goroutine 9 读取数据结束,读到数据358
读goroutine 4 正在读数据
读goroutine 4 读取数据结束,读到数据358
读goroutine 10 读取数据结束,读到数据358
写goroutine 5 正在写数据
写goroutine 5 写数据结束,写入新值358
读goroutine 8 正在读数据
读goroutine 8 读取数据结束,读到数据358
写goroutine 8 正在写数据
写goroutine 8 写数据结束,写入新值358
写goroutine 3 正在写数据
写goroutine 3 写数据结束,写入新值290
写goroutine 4 正在写数据
写goroutine 4 写数据结束,写入新值23
写goroutine 6 正在写数据
写goroutine 6 写数据结束,写入新值332
写goroutine 7 正在写数据
写goroutine 7 写数据结束,写入新值117
写goroutine 9 正在写数据
写goroutine 9 写数据结束,写入新值480
写goroutine 1 正在写数据
写goroutine 1 写数据结束,写入新值85
写goroutine 2 正在写数据
写goroutine 2 写数据结束,写入新值162

从上面的运行结果我们可以看到,读操作是可以同时进行的。但是写操作只能一个一个的进行。

4.Cond

Cond实现了一种条件变量,主要用来解决多个读协程等待共享资源变成ready的场景。在使用Cond的时候,需要特别注意下:每个Cond都会关联一个Lock(*sync.Mutex or *sync.RWMutex),当修改条件或者调用Wait方法时,必须加锁。Cond主要有三个函数构成,Broadcast(), Signal(), Wait()

4.1 broadcast

用来唤醒所有的处于等待状态的协程,如果没有等待的协程,该函数也不会报错。

// Broadcast wakes all goroutines waiting on c.
//
// It is allowed but not required for the caller to hold c.L
// during the call.
func (c *Cond) Broadcast() {
	c.checker.check()
	runtime_notifyListNotifyAll(&c.notify)
}

4.2 signal

通知单个等待状态的协程,让它继续执行,如果此时有多个协程处于等待状态,会从等待列表中取出最开始等待的那个协程,来接收消息。

// Signal wakes one goroutine waiting on c, if there is any.
//
// It is allowed but not required for the caller to hold c.L
// during the call.
func (c *Cond) Signal() {
	c.checker.check()
	runtime_notifyListNotifyOne(&c.notify)
}

4.3 wait

该函数在被调用之后,在没有收到Signal或者Broadcast的通知之前,协程处于阻塞状态。

// Wait atomically unlocks c.L and suspends execution
// of the calling goroutine. After later resuming execution,
// Wait locks c.L before returning. Unlike in other systems,
// Wait cannot return unless awoken by Broadcast or Signal.
//
// Because c.L is not locked when Wait first resumes, the caller
// typically cannot assume that the condition is true when
// Wait returns. Instead, the caller should Wait in a loop:
//
//    c.L.Lock()
//    for !condition() {
//        c.Wait()
//    }
//    ... make use of condition ...
//    c.L.Unlock()
//
func (c *Cond) Wait() {
	c.checker.check()
	t := runtime_notifyListAdd(&c.notify)
	c.L.Unlock()
	runtime_notifyListWait(&c.notify, t)
	c.L.Lock()
}

4.4 例子

package main

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

var locker = new(sync.Mutex)
var cond = sync.NewCond(locker)

func Test(x int) {
	cond.L.Lock()
	fmt.Println("aaa:", x)
	cond.Wait()
	fmt.Println("bbb:", x)
	time.Sleep(time.Second * 1)
	cond.L.Unlock()
}

func main() {
	for i := 0; i < 5; i++ {
		go Test(i)
	}

	fmt.Println("start all")
	time.Sleep(time.Second * 1)  // 使所有Goroutine进入wait状态
	cond.Signal()  // 唤醒单个锁
	time.Sleep(time.Second * 1)
	cond.Signal()
	time.Sleep(time.Second * 1)
	fmt.Println("broadcast")
	cond.Broadcast()  // 唤醒所有锁
	time.Sleep(time.Second * 3)
	fmt.Println("finish all")
}

执行结果为:

aaa: 0
aaa: 2
aaa: 1
aaa: 3
aaa: 4
start all
bbb: 0
bbb: 2
broadcast
bbb: 4
bbb: 1
bbb: 3
finish all

5. Once

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

5.1 实例

package main

import (
	"sync"
	"fmt"
)

var count = 0
var once sync.Once
var wg sync.WaitGroup

func main() {
	increment := func() {
		count ++
	}
	wg.Add(100)
	for i := 0; i < 100; i++ {
		go func() {
			defer wg.Done()
			once.Do(increment)
		}()
	}

	wg.Wait()
	fmt.Println("count is ", count)
}

输出结果为:

count is  1

第二个实例:

package main

import (
	"sync"
	"fmt"
)

var count int

func main() {
	increment := func() { count ++ }
	decrement := func() { count -- }

	var once sync.Once
	once.Do(increment)
	once.Do(decrement)

	fmt.Println("count is ", count)
}

输出结果:

count is  1

这说明sync.Once只计算调用Do方法的次数,而不是多少次唯一的调用Do方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值