go学习笔记-sync包

go学习笔记-sync包

sync包提供了基本的同步基元,如互斥锁。除了Once和WaitGroup类型,大部分都是适用于低水平程序线程,高水平的同步使用channel通信更好一些。

本包的类型的值不应被拷贝。

1. Locker接口

type Locker

type Locker interface {
    Lock()
    Unlock()
}

Locker接口代表一个可以加锁和解锁的对象。

1.1 Mutex

type Mutex

type Mutex struct {
   state int32
   sema  uint32
}

Mutex是一个互斥锁,可以创建为其他结构体的字段;

零值为解锁状态。

Mutex类型的锁和线程无关,可以由不同的线程加锁和解锁。

第一次使用后不得复制互斥锁。

1.1.1 加锁方法

*func (Mutex) Lock

func (m *Mutex) Lock() {
   // Fast path: grab unlocked mutex.
   if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
      if race.Enabled {
         race.Acquire(unsafe.Pointer(m))
      }
      return
   }
   // Slow path (outlined so that the fast path can be inlined)
   m.lockSlow()
}

Lock方法锁住m,如果m已经加锁,则阻塞直到m解锁。

1.1.1 解锁方法

*func (Mutex) Unlock

func (m *Mutex) Unlock() {
   if race.Enabled {
      _ = m.state
      race.Release(unsafe.Pointer(m))
   }

   // Fast path: drop lock bit.
   new := atomic.AddInt32(&m.state, -mutexLocked)
   if new != 0 {
      // Outlined slow path to allow inlining the fast path.
      // To hide unlockSlow during tracing we skip one extra frame when tracing GoUnblock.
      m.unlockSlow(new)
   }
}

Unlock方法解锁m,如果m未加锁会导致运行时错误。锁和线程无关,可以由不同的线程加锁和解锁。

1.1.2 使用例子

package main

import (
	"fmt"
	"sync"
)

var (
	lock  = sync.Mutex{}
	count = 0
	wg    sync.WaitGroup
)

func main() {
	wg.Add(2)
	go add()
	go add()
	wg.Wait()
	// 打印结果
	fmt.Println(count)
}

// add方法
func add() {
	for i := 0; i < 5000; i++ {
        lock.Lock()
		count = count + 1
		lock.Unlock()
	}
	wg.Done()
}

结果:

10000

1.2 RWMutex

type RWMutex struct

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
}

RWMutex是读写互斥锁。

该锁可以被同时多个读取者持有或唯一个写入者持有。

RWMutex可以创建为其他结构体的字段;零值为解锁状态。

RWMutex类型的锁也和线程无关,可以由不同的线程加读取锁/写入和解读取锁/写入锁。

1.2.1 加解锁方法

*func (RWMutex) Lock

func (rw *RWMutex) Lock() {
   if race.Enabled {
      _ = rw.w.state
      race.Disable()
   }
   // First, resolve competition with other writers.
   rw.w.Lock()
   // Announce to readers there is a pending writer.
   r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
   // Wait for active readers.
   if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
      runtime_SemacquireMutex(&rw.writerSem, false, 0)
   }
   if race.Enabled {
      race.Enable()
      race.Acquire(unsafe.Pointer(&rw.readerSem))
      race.Acquire(unsafe.Pointer(&rw.writerSem))
   }
}

Lock方法将rw锁定为写入状态,禁止其他线程读取或者写入。

*func (RWMutex) Unlock

func (rw *RWMutex) Unlock() {
   if race.Enabled {
      _ = rw.w.state
      race.Release(unsafe.Pointer(&rw.readerSem))
      race.Disable()
   }

   // Announce to readers there is no active writer.
   r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
   if r >= rwmutexMaxReaders {
      race.Enable()
      throw("sync: Unlock of unlocked RWMutex")
   }
   // Unblock blocked readers, if any.
   for i := 0; i < int(r); i++ {
      runtime_Semrelease(&rw.readerSem, false, 0)
   }
   // Allow other writers to proceed.
   rw.w.Unlock()
   if race.Enabled {
      race.Enable()
   }
}

Unlock方法解除rw的写入锁状态,如果m未加写入锁会导致运行时错误。

*func (RWMutex) RLock

func (rw *RWMutex) RLock() {
   if race.Enabled {
      _ = rw.w.state
      race.Disable()
   }
   if atomic.AddInt32(&rw.readerCount, 1) < 0 {
      // A writer is pending, wait for it.
      runtime_SemacquireMutex(&rw.readerSem, false, 0)
   }
   if race.Enabled {
      race.Enable()
      race.Acquire(unsafe.Pointer(&rw.readerSem))
   }
}

RLock方法将rw锁定为读取状态,禁止其他线程写入,但不禁止读取。

func (RWMutex) RUnlock*

func (rw *RWMutex) RUnlock() {
   if race.Enabled {
      _ = rw.w.state
      race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
      race.Disable()
   }
   if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
      // Outlined slow-path to allow the fast-path to be inlined
      rw.rUnlockSlow(r)
   }
   if race.Enabled {
      race.Enable()
   }
}

Runlock方法解除rw的读取锁状态,如果m未加读取锁会导致运行时错误。

*func (RWMutex) RLocker

func (rw *RWMutex) RLocker() Locker {
   return (*rlocker)(rw)
}

Rlocker方法返回一个互斥锁,通过调用rw.Rlock和rw.Runlock实现了Locker接口。

1.2.2 使用例子

package main

import (
   "fmt"
   "sync"
)

var (
   lock       = sync.RWMutex{}
   data       = make(map[int]int)
   cacheValid = false
   wg         = sync.WaitGroup{}
)

func main() {
   for i := 0; i < 100; i++ {
      wg.Add(1)
      i := i
      go func() {
         CachedData(i)
      }()
   }

   // 干扰缓存方法,模拟缓存失效
   for i := 0; i < 1000; i++ {
      go func() {
         cacheValid = false
      }()
   }
   wg.Wait()
   fmt.Println(data)
}

// 模拟缓存数据
func CachedData(i int) {
   fmt.Println(i)
   defer wg.Done()
   // 获取读锁
   lock.RLock()
   // 如果缓存失效,那表示需要重新去获取数据写入缓存,所以就得变成写锁
   if !cacheValid {
      lock.RUnlock()
      lock.Lock()
      // (重新检查状态,因为有可能别的协程已经在当前协程获取写锁时已经更新了缓存)
      if !cacheValid {
         data[i] = i
         cacheValid = true
      }
      lock.Unlock()
   } else {
      // do something...
      // 业务逻辑

      // 释放读锁
      lock.RUnlock()
   }

}

2 Once

*func (Once) Do

type Once struct {
	// done indicates whether the action has been performed.
	// It is first in the struct because it is used in the hot path.
	// The hot path is inlined at every call site.
	// Placing done first allows more compact instructions on some architectures (amd64/386),
	// and fewer instructions (to calculate offset) on other architectures.
	done uint32
	m    Mutex
}

Once是只执行一次动作的对象。

2.1 Do方法

func (o *Once) Do(f func()) {
	// Note: Here is an incorrect implementation of Do:
	//
	//	if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
	//		f()
	//	}
	//
	// Do guarantees that when it returns, f has finished.
	// This implementation would not implement that guarantee:
	// given two simultaneous calls, the winner of the cas would
	// call f, and the second would return immediately, without
	// waiting for the first's call to f to complete.
	// This is why the slow path falls back to a mutex, and why
	// the atomic.StoreUint32 must be delayed until after f returns.

	if atomic.LoadUint32(&o.done) == 0 {
		// Outlined slow-path to allow inlining of the fast-path.
		o.doSlow(f)
	}
}

func (o *Once) doSlow(f func()) {
	o.m.Lock()
	defer o.m.Unlock()
	if o.done == 0 {
		defer atomic.StoreUint32(&o.done, 1)
		f()
	}
}

Do方法当且仅当第一次被调用时才执行函数f。换句话说,给定变量:

var once Once

如果once.Do(f)被多次调用,只有第一次调用会执行f,即使f每次调用Do 提供的f值不同。需要给每个要执行仅一次的函数都建立一个Once类型的实例。

2.2 单列实现方式之一

package main

import (
	"fmt"
	"sync"
)

type Singleton struct{}

var (
	ins  *Singleton
	once sync.Once
)
// 获取单例
func GetIns() *Singleton {
    // 只有第一次调用Do方法才会执行func
	once.Do(func() {
		ins = &Singleton{}
	})
	return ins
}

3.Pool

type Pool struct {
	noCopy noCopy

	local     unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal
	localSize uintptr        // size of the local array

	victim     unsafe.Pointer // local from previous cycle
	victimSize uintptr        // size of victims array

	// 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指定一个函数在Get方法可能返回nil时来生成一个值
    //  该参数不能在调用Get方法时被修改
	New func() interface{}
}

Pool是一个可以分别存取的临时对象的集合。

**Pool中保存的任何item都可能随时不做通告的释放掉。**如果Pool持有该对象的唯一引用,这个item就可能被回收。

Pool可以安全的被多个线程同时使用。

Pool的目的是缓存申请但未使用的item用于之后的重用,以减轻GC的压力。也就是说,让创建高效而线程安全的空闲列表更容易。但Pool并不适用于所有空闲列表。

**Pool的合理用法是管理一组被多个独立并发线程共享并可能重用的临时item。**Pool提供了让多个线程分摊内存申请消耗的方法。

Pool的一个好例子在fmt包里。该Pool维护一个动态大小的临时输出缓存仓库。该仓库会在过载(许多线程活跃的打印时)增大,在沉寂时缩小。

另一方面,** 管理着短寿命对象的空闲列表不适合使用Pool,因为这种情况下内存申请消耗不能很好的分配。**这时应该由这些对象自己实现空闲列表。

第一次使用后不得复制池。

3.1 使用例子

package main

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

var pool = sync.Pool{
	New: func() interface{} {
		return "default"
	},
}

func main() {
	// Put方法将x放入池中。
	pool.Put("aaaa")
	pool.Put("bbbb")
	pool.Put("cccc")
	for i := 0; i < 10; i++ {
		go func() {
			// Get方法从池中选择任意一个item,删除其在池中的引用计数,并提供给调用者。
			// Get方法也可能选择无视内存池,将其当作空的。
			// 调用者不应认为Get的返回这和传递给Put的值之间有任何关系。
			get := pool.Get().(string)
			fmt.Println(get)
		}()
	}
	// 防止主协程退出程序结束
	time.Sleep(time.Second * 5)

}

4. WaitGroup

type WaitGroup struct {
   noCopy noCopy

   // 64-bit value: high 32 bits are counter, low 32 bits are waiter count.
   // 64-bit atomic operations require 64-bit alignment, but 32-bit
   // compilers do not ensure it. So we allocate 12 bytes and then use
   // the aligned 8 bytes in them as state, and the other 4 as storage
   // for the sema.
   state1 [3]uint32
}

WaitGroup用于等待一组线程的结束。父线程调用Add方法来设定应等待的线程的数量。每个被等待的线程在结束时应调用Done方法。同时,主线程里可以调用Wait方法阻塞至所有线程结束。

4.1 使用例子

package main

import (
	"fmt"
	"sync"
)

func main() {
	wg := sync.WaitGroup{}
	for i := 0; i < 100; i++ {
		// Add方法向内部计数加上delta,delta可以是负数
		// 注意Add加上正数的调用应在Wait之前,否则Wait可能只会等待很少的线程。一般来说本方法应在创建新的线程或者其他应等待的事件之前调用。
		wg.Add(1)
		go work(&wg)
	}

	// Wait方法阻塞直到WaitGroup计数器减为0。
    // 如果内部计数器变为0,Wait方法阻塞等待的所有线程都会释放,如果计数器小于0,方法panic。
	wg.Wait()
	fmt.Println("done!")
}

// work方法
func work(wg *sync.WaitGroup) {
	// Done方法减少WaitGroup计数器的值,应在线程的最后执行。
	defer wg.Done()
	fmt.Println("do my work..")
}

5. Cond

type Cond struct {
   noCopy noCopy

   // L is held while observing or changing the condition
   L Locker

   notify  notifyList
   checker copyChecker
}

Cond实现了一个条件变量,一个线程集合地,供线程等待或者宣布某事件的发生。

每个Cond实例都有一个相关的锁(一般是Mutex或RWMutex类型的值),它必须在改变条件时或者调用Wait方法时保持锁定。Cond可以创建为其他结构体的字段,Cond在开始使用后不能被拷贝。

未完待续。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值