Go语言-并发控制和锁

目录

1、互斥锁

2、读写互斥锁

3、sync.Once

4、sync.Map

5、定时器


1、互斥锁

互斥锁是一种常用的控制共享资源访问的方法,它能够保证只有一个 goroutine 访问共享资源。

互斥锁作用:同一时间有且仅有一个 goroutine 进入临界区,其他 goroutine 则在等待锁,等互斥锁释放后,等待的 goroutine 才可以获取锁进入临界区,多个 goroutine 都在等待一个锁时,唤醒机制是随机的。

示例:

package main

import (
	"fmt"
	"sync"
)

//全局变量
var x int64

//计数器
var sw sync.WaitGroup

//互斥锁
var lock sync.Mutex

func add() {
	defer sw.Done()
	for i := 0; i < 5000; i++ {
		lock.Lock() 		//加锁
		x++
		lock.Unlock()		//解锁
	}

}

func main() {
	sw.Add(2)
	go add()
	go add()
	sw.Wait()
	fmt.Println(x)
}



//输出结果如下
10000

2、读写互斥锁

互斥锁是完全互斥的,但是很多的实际场景,读多写少,当并发的去读取一个资源不涉及资源修改的时候是没有必要加锁的,这种场景使用读写锁是更好的一种选择,可以提高性能。

读写锁分两种:读锁和写锁。当一个 goroutine 获取读锁之后,其他的 goroutine 如果是获取读锁会继续获得锁,如果是获取写锁就会等待;当一个 goroutine 获取写锁之后,其他的 goroutine 无论是获取读锁还是写锁都会等待。

示例:

package main

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

//锁执行时间测试

var x int64

//计数器
var sw sync.WaitGroup

//互斥锁
var lock sync.Mutex

//读写锁
var rwLock sync.RWMutex

//读函数
func read() {
	defer sw.Done()
	rwLock.RLock() //读锁加锁
	//读取时间1毫秒
	time.Sleep(time.Microsecond)
	rwLock.RUnlock() //读锁解锁
}

//写函数
func write() {
	defer sw.Done()
	rwLock.Lock() //写锁加锁
	//写入5毫秒
	x++
	time.Sleep(time.Millisecond * 5)
	rwLock.Unlock() //写锁解锁
}

func main() {
	//启动时间戳
	start := time.Now()
	//写入10次
	for i := 0; i < 10; i++ {
		sw.Add(1)
		go write()
	}
	//读取1000次
	for i := 0; i < 1000; i++ {
		sw.Add(1)
		go read()
	}
	sw.Wait()
	end := time.Now()
	fmt.Printf("使用时间: %v", end.Sub(start))
}


//输出结果如下
使用时间: 165.518ms

3、sync.Once

sync.Once 用于延迟一个开销很大的初始化操作,到真正用到它的时候再执行。

例如,定义了一个 init 初始化函数,程序启动的时候会被自动加载,无论是否用到都会加载,这样程序就会增加程序的启动延时。

示例:

package main

//龙类
type Dragon struct {
	name     string
	property string
}

//加载龙类
func loadDragon(n, p string) Dragon {
	dragon := Dragon{
		name:     n,
		property: p,
	}
	return dragon
}

var cards map[string]Dragon

//卡牌
func loadCards() {
	cards = map[string]Dragon{
		"red":   loadDragon("红龙", "火"),
		"bule":  loadDragon("蓝龙", "冰"),
		"white": loadDragon("白龙", "光"),
		"black": loadDragon("黑龙", "暗"),
	}
}

//被多个goroutine调用时不是并发安全的
func card(name string) Dragon {
	if cards == nil {
		loadCards()
	}
	return cards[name]
}

多个goroutine并发调动card函数不是并发安全的,现代的编译器和CPU可能会在保证每个goroutine都满足串行一致的基础上,自由重排访问内存的顺序。loadCards函数可能会被重排。

 func loadCards()  {
	 cards =make(map[string]Dragon)
	 cards["red"]=loadDragon("红龙","火")
	 cards["blue"]=loadDragon("蓝龙","冰")
	 cards["white"]=loadDragon("白龙","光")
	 cards["black"]=loadDragon("黑龙","暗")
 }

这种情况下会出现即使判断了 cards 不是 nil 也不意味着初始化完成。

考虑到这种情况,解决的办法就是添加互斥锁,保证初始化 cards 的时候不会被其他 goroutine 操作,单这样做又会引发性能问题。

于是Go 提供了一种解决方案,sync.Once

sync.Once只有一个Do方法。

示例:

//定义sync.Once
var onlyOne sync.Once

//被多个goroutine调用时并不是并发安全的
func card(name string) Dragon {
	if cards == nil {
		onlyOne.Do(loadCards)
	}
	return cards[name]
}

示例:sync.Once调用带参函数。

package main

import (
	"fmt"
	"sync"
)

//sync.Once
var onlyOne sync.Once

func test(x int) {
	fmt.Println(x)
}

//闭包
func closer(x int) func() {
	return func() {
		test(x)
	}
}

func main() {
	//函数变量
	t := closer(10)
	onlyOne.Do(t)
}


//输出结果如下
10

4、sync.Map

示例:

package main

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

var m = make(map[string]int)

//设置map
func set(key string, value int) {
	m[key] = value
}

//获取map值
func get(key string) int {
	return m[key]
}

func main() {
	sw := sync.WaitGroup{}
	for i := 0; i < 10; i++ {
		sw.Add(1)
		//匿名函数
		go func(n int) {
			key := strconv.Itoa(n) //整形转字符串
			set(key, n)
			fmt.Printf("k:%s,v:%v\n", key, get(key))
			sw.Done()
		}(i)
	}
	sw.Wait()
}

运行结果如下:

在这里插入图片描述

针对这种场景,就需要给map加锁保证并发安全,Go提供了一种开箱即用的安全map,就是 sync.Map 。

sync.Map不需要像内置map一样需要初始化,直接就能使用。

它的内部定义了,Store,Load,LoadOrStore,Delete,Range等操作方法。

package main

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

var m = sync.Map{}

func main() {
	sw := sync.WaitGroup{}
	for i := 0; i < 10; i++ {
		sw.Add(1)
		//匿名函数
		go func(n int) {
			key := strconv.Itoa(n) //整形转字符串
			m.Store(key, n)
			value, _ := m.Load(key)
			fmt.Printf("key:%s,value:%v\n", key, value)
			sw.Done()
		}(i)
	}
	sw.Wait()
}

//输出结果如下
key:9,value:9
key:8,value:8
key:1,value:1
key:0,value:0
key:3,value:3
key:6,value:6
key:4,value:4
key:2,value:2
key:5,value:5
key:7,value:7

5、定时器

指定间隔时间执行任务。

package main

import (
	"fmt"
	"time"
)

//定时器
func tickDemo() {
	ticker := time.Tick(time.Second) //定义1秒间隔定时器
	for i := range ticker {
		fmt.Println(i) //每秒都会执行任务
	}
}

func main() {
	tickDemo()
}


//输出结果如下
2022-04-27 17:48:22.2357747 +0800 CST m=+1.012006201
2022-04-27 17:48:23.2406837 +0800 CST m=+2.016915201
2022-04-27 17:48:24.2391351 +0800 CST m=+3.015366601
...
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值