Go:官方库 - Sync

参考:Go语言中文网
参考:浅谈 Golang sync 包的相关使用方法

       老实说,看了go提供的这些锁的包,和java中的concurrent包其实很多作用是类似的,所以说其实很多语言的基础架构都是类似的,理解到源码层次再回头去学习其他的语言就不会那么难了。

壹、Mutex - 互斥锁

1、方法说明

1、结构体

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

	// A Mutex is a mutual exclusion lock.
	// The zero value for a Mutex is an unlocked mutex.
	//
	// A Mutex must not be copied after first use.
	type Mutex struct {
		state int32
		sema  uint32	
	}

2、Lock()

       对资源进行加锁,如果没有获取到锁则会一直锁定,等待锁释放,再进行获取

	// Lock locks m.
	// If the lock is already in use, the calling goroutine
	// blocks until the mutex is available.
	func (m *Mutex) Lock() 

3、Unlock()

       释放锁,如果没有被锁定而调用这个方法就会报错。

	// Unlock unlocks m.
	// It is a run-time error if m is not locked on entry to Unlock.
	//
	// A locked Mutex is not associated with a particular goroutine.
	// It is allowed for one goroutine to lock a Mutex and then
	// arrange for another goroutine to unlock it.
	func (m *Mutex) Unlock()

2、使用示例

       加锁的意思是:锁定互斥锁,而不是去锁定一段代码。也就是说,当代码执行到有锁的地方时,它获取不到互斥锁的锁定,会阻塞在那里,从而达到控制同步的目的。

	package main

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


贰、RWMutex - 读写锁

1、方法说明

读写锁的锁定会遵循一些规则:

  1. 同时只能有一个 goroutine 能够获得写锁定。
  2. 同时可以有任意多个 gorouinte 获得读锁定。
  3. 同时只能存在写锁定或读锁定(读和写互斥)。
	// There is a modified copy of this file in runtime/rwmutex.go.
	// If you make any changes here, see if you should make them there.
	
	// A RWMutex is a reader/writer mutual exclusion lock.
	// The lock can be held by an arbitrary number of readers or a single writer.
	// The zero value for a RWMutex is an unlocked mutex.
	//
	// A RWMutex must not be copied after first use.
	//
	// If a goroutine holds a RWMutex for reading and another goroutine might
	// call Lock, no goroutine should expect to be able to acquire a read lock
	// until the initial read lock is released. In particular, this prohibits
	// recursive read locking. This is to ensure that the lock eventually becomes
	// available; a blocked Lock call excludes new readers from acquiring the
	// lock.
	type RWMutex struct {
		w           Mutex  // held if there are pending writers
		writerSem   uint32 // semaphore for writers to wait for completing readers
		readerSem   uint32 // semaphore for readers to wait for completing writers
		readerCount int32  // number of pending readers
		readerWait  int32  // number of departing readers
	}

1、Lock()

       对写操作进行锁定

	// Lock locks rw for writing.
	// If the lock is already locked for reading or writing,
	// Lock blocks until the lock is available.
	func (rw *RWMutex) Lock() 

2、UnLock()

       对写操作进行释放锁

	// Unlock unlocks rw for writing. It is a run-time error if rw is
	// not locked for writing on entry to Unlock.
	//
	// As with Mutexes, a locked RWMutex is not associated with a particular
	// goroutine. One goroutine may RLock (Lock) a RWMutex and then
	// arrange for another goroutine to RUnlock (Unlock) it.
	func (rw *RWMutex) Unlock() 

3、RLock()

       对读操作进行加锁

	// RLock locks rw for reading.
	//
	// It should not be used for recursive read locking; a blocked Lock
	// call excludes new readers from acquiring the lock. See the
	// documentation on the RWMutex type.
	func (rw *RWMutex) RLock()

4、RUnlock

       对读操作进行释放锁

	// RUnlock undoes a single RLock call;
	// it does not affect other simultaneous readers.
	// It is a run-time error if rw is not locked for reading
	// on entry to RUnlock.
	func (rw *RWMutex) RUnlock()

2、实际示例

       分别开启50个读写协程,模拟读写争取读写锁的情况。

	package main
	
	import (
		"fmt"
		"math/rand"
		"sync"
	)
	
	var count int
	var rw sync.RWMutex
	
	func main() {
		ch := make(chan struct{}, 10)
		for i := 0; i < 50; i++ {
			go read(i, ch)
		}
		for i := 0; i < 50; i++ {
			go write(i, ch)
		}
	
		for i := 0; i < 10; i++ {
			<-ch
		}
	}
	
	func read(n int, ch chan struct{}) {
		rw.RLock()
		fmt.Printf("goroutine %d 进入读操作...\n", n)
		v := count
		fmt.Printf("goroutine %d 读取结束,值为:%d\n", n, v)
		rw.RUnlock()
		ch <- struct{}{}
	}
	
	func write(n int, ch chan struct{}) {
		rw.Lock()
		fmt.Printf("goroutine %d 进入写操作...\n", n)
		v := rand.Intn(1000)
		count = v
		fmt.Printf("goroutine %d 写入结束,新值为:%d\n", n, v)
		rw.Unlock()
		ch <- struct{}{}
	}

叁、WaitGroup

1、方法说明

       WaitGroup 用于等待一组 goroutine 结束,也就是说让一组操作同时结束之后才进行下一步的操作。跟java中的CyclicBarrier有点类似

	// A WaitGroup waits for a collection of goroutines to finish.
	// The main goroutine calls Add to set the number of
	// goroutines to wait for. Then each of the goroutines
	// runs and calls Done when finished. At the same time,
	// Wait can be used to block until all goroutines have finished.
	//
	// A WaitGroup must not be copied after first use.
	
	type WaitGroup struct {
		noCopy noCopy
		state1 [3]uint32
	}

1、Add()

       添加要等待的协程数量,如果传入负数会报错

	// Add adds delta, which may be negative, to the WaitGroup counter.
	// If the counter becomes zero, all goroutines blocked on Wait are released.
	// If the counter goes negative, Add panics.
	//
	// Note that calls with a positive delta that occur when the counter is zero
	// must happen before a Wait. Calls with a negative delta, or calls with a
	// positive delta that start when the counter is greater than zero, may happen
	// at any time.
	// Typically this means the calls to Add should execute before the statement
	// creating the goroutine or other event to be waited for.
	// If a WaitGroup is reused to wait for several independent sets of events,
	// new Add calls must happen after all previous Wait calls have returned.
	// See the WaitGroup example.
	func (wg *WaitGroup) Add(delta int) 

2、Done()

       运行完一个协程,等待的队列数量减一。

	// Done decrements the WaitGroup counter by one.
	func (wg *WaitGroup) Done()

3、Wait()

       等待队列中协程数量等于0,才运行下面的代码。

	// Wait blocks until the WaitGroup counter is zero.
	func (wg *WaitGroup) Wait() 

2、实际示例

       每个协程等待5秒,直到所有的协程运行完毕,才会执行wait下面的代码。

	package main
	
	import (
		"fmt"
		"sync"
		"time"
	)
	
	func main() {
		var wg sync.WaitGroup
	
		for i, s := range seconds {
			// 计数加 1
			wg.Add(1)
			go func(i, s int) {
				// 计数减 1
				defer wg.Done()
				fmt.Printf("goroutine%d 结束\n", i)
				time.Sleep(5 * time.Second)
			}(i, s)
		}
	
		// 等待执行结束
		wg.Wait()
		fmt.Println("所有 goroutine 执行结束")
	}

肆、Once - 只执行一次

1、方法说明

       使用 sync.Once 对象可以使得函数多次调用只执行一次。

	// Once is an object that will perform exactly one action.
	type Once struct 

       只有在第一次调用的时候才会被执行,后面重复调用不会执行

	// Do calls the function f if and only if Do is being called for the
	// first time for this instance of Once. In other words, given
	// 	var once Once
	// if once.Do(f) is called multiple times, only the first call will invoke f,
	// even if f has a different value in each invocation. A new instance of
	// Once is required for each function to execute.
	//
	// Do is intended for initialization that must be run exactly once. Since f
	// is niladic, it may be necessary to use a function literal to capture the
	// arguments to a function to be invoked by Do:
	// 	config.once.Do(func() { config.init(filename) })
	//
	// Because no call to Do returns until the one call to f returns, if f causes
	// Do to be called, it will deadlock.
	//
	// If f panics, Do considers it to have returned; future calls of Do return
	// without calling f.
	//
	func (o *Once) Do(f func())

2、实际示例

	package main

	import (
	    "fmt"
	    "sync"
	)
	
	func main() {
	    var once sync.Once
	    onceBody := func() {
	        fmt.Println("Only once")
	    }
	    done := make(chan bool)
	    for i := 0; i < 10; i++ {
	        go func() {
	            once.Do(onceBody)
	            done <- true
	        }()
	    }
	    for i := 0; i < 10; i++ {
	        <-done
	    }
	}

伍、Pool - 临时对象池

1、方法说明

参考:深入Golang之sync.Pool详解
       我们通常用golang来构建高并发场景下的应用,但是由于golang内建的GC机制会影响应用的性能,为了减少GC,golang提供了对象重用的机制,也就是sync.Pool对象池。 sync.Pool是可伸缩的,并发安全的。其大小仅受限于内存的大小,可以被看作是一个存放可重用对象的值的容器。 设计的目的是存放已经分配的但是暂时不用的对象,在需要用到的时候直接从pool中取。

       任何存放区其中的值可以在任何时候被删除而不通知,在高负载下可以动态的扩容,在不活跃时对象池会收缩。

参考:Go语言sync.Pool(线程池)使用

使用说明:

  • sync.Pool是一个可以存或取的临时对象集合
  • sync.Pool可以安全被多个线程同时使用,保证线程安全
  • 注意、注意、注意,sync.Pool中保存的任何项都可能随时不做通知的释放掉,所以不适合用于像- socket长连接或数据库连接池。
  • sync.Pool主要用途是增加临时对象的重用率,减少GC负担。

1、New

       当获取不到临时对象时自动创建一个(不会主动加入到 Pool 中),即如果取不到,默认的返回值。

	// New optionally specifies a function to generate
	// a value when Get would otherwise return nil.
	// It may not be changed concurrently with calls to Get.
	New func() interface{}

2、Get()

       从临时对象池取出数据

	// Get selects an arbitrary item from the Pool, removes it from the
	// Pool, and returns it to the caller.
	// Get may choose to ignore the pool and treat it as empty.
	// Callers should not assume any relation between values passed to Put and
	// the values returned by Get.
	//
	// If Get would otherwise return nil and p.New is non-nil, Get returns
	// the result of calling p.New.
	func (p *Pool) Get() interface{}

3、Put()

       向临时对象添加数据

	// Put adds x to the pool.
	func (p *Pool) Put(x interface{})

2、实际示例

	package main
	
	import (
		"fmt"
		"sync"
	)
	
	func main() {
		p := &sync.Pool{
			New: func() interface{} {
				return 0
			},
		}
		p.Put("jiangzhou")
		p.Put(123456)
		fmt.Println(p.Get())
		fmt.Println(p.Get())
		fmt.Println(p.Get())
	}

陆、Cond - 条件锁

参考:Golang sync.Cond 简介与用法

1、方法说明

       Cond 实现了一个条件变量,在 Locker 的基础上增加的一个消息通知的功能,保存了一个通知列表,用来唤醒一个或所有因等待条件变量而阻塞的 Go 程,以此来实现多个 Go 程间的同步。

	// Cond implements a condition variable, a rendezvous point
	// for goroutines waiting for or announcing the occurrence
	// of an event.
	//
	// Each Cond has an associated Locker L (often a *Mutex or *RWMutex),
	// which must be held when changing the condition and
	// when calling the Wait method.
	//
	// A Cond must not be copied after first use.
	type Cond struct

1、NewCond()

       创建一个带锁的条件变量,Locker 通常是一个 *Mutex 或 *RWMutex

	func NewCond(l Locker) *Cond

2、Broadcast()

       唤醒所有因等待条件变量 c 阻塞的 goroutine

	func (c *Cond) Broadcast()

3、Signal()

       唤醒一个因等待条件变量 c 阻塞的 goroutine

	func (c *Cond) Signal()

4、Wait()

       等待 c.L 解锁并挂起 goroutine,在稍后恢复执行后,Wait 返回前锁定 c.L,只有当被 Broadcast 和 Signal 唤醒,Wait 才能返回

	func (c *Cond) Wait()

2、实际示例

       关键的地方在于配套使用,也就是说wait的协程会等待signal来唤醒,如果没有唤醒则会一直阻塞。

	package main

	import (
		"fmt"
		"sync"
		"time"
	)
	
	var locker = new(sync.Mutex)
	var cond = sync.NewCond(locker)
	
	func main() {
		for i := 0; i < 10; i++ {
			go func(x int) {
				cond.L.Lock()         //获取锁
				defer cond.L.Unlock() //释放锁
				cond.Wait()           //等待通知,阻塞当前goroutine
				fmt.Println(x)
			}(i)
		}
		time.Sleep(time.Second * 1) // 睡眠1秒,使所有goroutine进入 Wait 阻塞状态
		fmt.Println("Signal...")
		cond.Signal() // 1秒后下发一个通知给已经获取锁的goroutine
		time.Sleep(time.Second * 1)
		fmt.Println("Signal...")
		cond.Signal() // 1秒后下发下一个通知给已经获取锁的goroutine
		time.Sleep(time.Second * 1)
		cond.Broadcast() // 1秒后下发广播给所有等待的goroutine
		fmt.Println("Broadcast...")
		time.Sleep(time.Second * 1) // 睡眠1秒,等待所有goroutine执行完毕
	}
发布了117 篇原创文章 · 获赞 15 · 访问量 5617

猜你喜欢

转载自blog.csdn.net/qq_34326321/article/details/104818533
今日推荐