Go语言基础之并发(并发安全和锁)

文章目录

  • 互斥锁
  • 读写锁
  • sync.Once实现单例模式
  • sync.Map
  • atomic原子变量

互斥锁

使用互斥锁能够保证同一时间有且只有一个goroutine进入临界区,其他的goroutine则在等待锁;当互斥锁释放后,等待的goroutine才可以获取锁进入临界区,多个goroutine同时等待一个锁时,唤醒的策略是随机的。

package main

import (
	"fmt"
	"sync"
)

var global int64
var wg sync.WaitGroup
var lock sync.Mutex

func add() {
	defer wg.Done()
	for i := 0; i < 5000; i++ {
		lock.Lock() // 加锁
		global++
		lock.Unlock() // 解锁
	}
}
func main() {
	wg.Add(2)
	go add()
	go add()
	wg.Wait()
	fmt.Println(global)
}

读写锁

对于读多写少的场景下使用读写锁性能会比互斥锁好。
写锁优先级高,写锁独占,读锁共享。

package main

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

var (
	wg     sync.WaitGroup
	lock   sync.Mutex
	rwlock sync.RWMutex
)

func write() {
	// lock.Lock()   // 加互斥锁
	rwlock.Lock()                     // 加写锁
	time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒
	rwlock.Unlock()                   // 解写锁
	// lock.Unlock()                     // 解互斥锁
	wg.Done()
}

func read() {
	// lock.Lock()                  // 加互斥锁
	rwlock.RLock()               // 加读锁
	time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒
	rwlock.RUnlock()             // 解读锁
	// lock.Unlock()                // 解互斥锁
	wg.Done()
}

func main() {
	start := time.Now()
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go write()
	}

	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go read()
	}

	wg.Wait()
	end := time.Now()
	fmt.Println(end.Sub(start))
}

在代码中生硬的使用time.Sleep肯定是不合适的,Go语言中可以使用sync.WaitGroup来实现并发任务的同步。

sync.Once实现单例模式

Go语言中的sync包中提供了一个针对只执行一次场景的解决方案–sync.Once。
sync.Once只有一个Do方法,其签名如下:

func (o *Once) Do(f func()) {}

备注:如果要执行的函数f需要传递参数就需要搭配闭包来使用。

package main

import "sync"

type singleton struct{}

var instance *singleton
var once sync.Once

func GetInstance() *singleton {
	once.Do(func() {
		instance = &singleton{}
	})
	return instance
}
func main() {
	GetInstance()
}

sync.Map

Go语言中内置的map不是并发安全的。

package main

import (
	"fmt"
	"strconv"
	"sync"
)

var safeMap = sync.Map{}

func main() {
	wg := sync.WaitGroup{}
	for i := 0; i < 20; i++ {
		go func(n int) {
			wg.Add(1)
			key := strconv.Itoa(n)
			safeMap.Store(key, n)
			val, ok := safeMap.Load(key) //
			if ok {
				fmt.Println("key:", key, "val:", val)
			}
			wg.Done()
		}(i)
	}
	wg.Wait()
}

普通的map则会报错。

atomic原子变量

测试一下对于变量自增操作,加锁、使用atomic原子变量三个的性能差别:

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

//Counter 定义一个公共的接口
type Counter interface{
	Increment()
	Load() int64
}
//NonLockCounter 无锁的自增变量结构体
type NonLockCounter struct{
	val int64
}
//Increment NonLockCounter结构体的自增方法
func(n *NonLockCounter)Increment(){
	n.val++
}
//Load NonLockCounter的Load方法
func(n *NonLockCounter)Load()int64{
	return n.val
}
//LockCounter 带互斥锁的自增变量结构体
type LockCounter struct{
	val int64
	lock sync.Mutex
}
//Increment 带互斥锁的自增变量结构体的自增方法
func(l *LockCounter)Increment(){
	l.lock.Lock()
	defer l.lock.Unlock()
	l.val++
}
//Load 带互斥锁的自增变量结构体的Load方法
func(l* LockCounter)Load()int64{
	l.lock.Lock()
	defer l.lock.Unlock()	
	return l.val
}
//AtomicCounter 使用atomic库的原子操作的结构体
type AtomicCounter struct{
	val int64
}
//Increment 原子操作的自增方法
func(a *AtomicCounter)Increment(){
	atomic.AddInt64(&a.val,1)
}
//Load 原子操作的Load方法
func(a *AtomicCounter)Load()int64{
	return atomic.LoadInt64(&a.val)
}

func test(c Counter){
	wg:=sync.WaitGroup{}
	start:=time.Now()
	for i:=0;i<1000;i++{
		wg.Add(1)
		go func(){
			c.Increment()
			wg.Done()
		}()
	}
	wg.Wait()
	end:=time.Now()
	fmt.Println(c.Load(),end.Sub(start))
}
func main(){
	c1:=NonLockCounter{}
	c2:=LockCounter{}
	c3:=AtomicCounter{}
	test(&c1)
	test(&c2)
	test(&c3)
}

971 1.9977ms
1000 1.001ms
1000 0s
atomic包提供了底层的原子级内存操作,对于同步算法的实现很有用。这些函数必须谨慎地保证正确使用。除了某些特殊的底层应用,使用通道或者sync包的函数/类型实现同步更好。

你可能感兴趣的:(Golang)