GO 中的Sync对于各种锁的原语

刚开始接触Go语言,跟的孟凡杰老师的课程进行学习,仅以博客作为记录,如有不对欢迎支出。

Go语言不仅仅提供基于CSP通讯模型,也支持基于共享内存的多线程数据访问。Go中提供了Sync的包提供的基本原语,常用的有如下几种:

  1. Mutex互斥锁
  2. RWMutex读写分离锁
  3. WaitGroup等待返回goroutine
  4. Once单实例
  5. Cond在满足一定条件时候唤醒goroutine

Mutex互斥锁

在读写的时候进行互斥,防止出现同时读和同时写的情况。

package main

import (
	"sync"
	"time"
)

func main() {
	//unsafeWrite()
	safeWrite()
	time.Sleep(time.Second)
}
func unsafeWrite() {
	conflictMap := map[int]int{}
	for i := 0; i < 100; i++ {
		go func() {
			conflictMap[1] = i
		}()
	}
}

type SafeMap struct {
	safeMap map[int]int
	sync.Mutex
}

func safeWrite() {
	s := SafeMap{
		safeMap: map[int]int{},
		Mutex:   sync.Mutex{},
	}
	for i := 0; i < 100; i++ {
		go func() {
			s.Write(1, 1)
		}()
	}
}

func (s *SafeMap) Read(k int) (int, bool) {
	s.Lock()
	defer s.Unlock()
	result, ok := s.safeMap[k]
	return result, ok
}

func (s *SafeMap) Write(k, v int) {
	s.Lock()
	defer s.Unlock()
	s.safeMap[k] = v
}

通过Lock和Unlock的方式来实现互斥的操作

RWMutex读写分离锁

要求多协程在执行的时候,读操作不会影响写,所以读不互斥,但是读写之间互斥,以及写之间互斥。

package main

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

func main() {
	go rLock()
	go wLock()
	go lock()
	time.Sleep(5 * time.Second)
}

func lock() {
	lock := sync.Mutex{}
	for i := 0; i < 3; i++ {
		lock.Lock()
		defer lock.Unlock()
		fmt.Println("lock:", i)
	}
}

func rLock() {
	lock := sync.RWMutex{}
	for i := 0; i < 3; i++ {
		lock.RLock()
		defer lock.RUnlock()
		fmt.Println("rLock:", i)
	}
}

func wLock() {
	lock := sync.RWMutex{}
	for i := 0; i < 3; i++ {
		lock.Lock()
		defer lock.Unlock()
		fmt.Println("wLock:", i)
	}
}

通过观察输出结果可以发现,R锁可以继续执行,因为之间不互斥,但是W锁不能继续执行,因为写之间是互斥的。

WaitGroup等待返回goroutine

在协程内部的goroutine在执行后都会Done掉,主协程会等待goroutine,当所有goroutine都完成了才会完整退出,这从时间上完美解决了,不知如何设置定时的问题。

ackage main

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

func main() {
	
	waitByWG()
}


func waitByWG() {
	wg := sync.WaitGroup{}
	wg.Add(100)
	for i := 0; i < 100; i++ {
		go func(i int) {
			fmt.Println(i)
			wg.Done()
		}(i)
	}
	wg.Wait()
}

Once单实例

同一个代码块可能被多次调用操作,但要求它是单实例,指实例化一次,通过Once即可完成操作。通过Once.Do的操作来执行。

package main

import (
	"fmt"
	"sync"
)

type SliceNum []int

func NewSlice() SliceNum {
	return make(SliceNum, 0)

}

func (s *SliceNum) Add(elem int) *SliceNum {
	*s = append(*s, elem)
	fmt.Println("add", elem)
	fmt.Println("add SliceNum end", s)
	return s
}

func main() {
	var once sync.Once
	s := NewSlice()
	// 看源代码理解once的行为
	once.Do(func() {
		s.Add(16)
	})
	once.Do(func() {
		s.Add(16)
	})
	once.Do(func() {
		s.Add(16)
	})
}

其中Once.Do的源码如下

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()
	}
}

通过一个doSlow的操作来实现这个逻辑,当每次这个done为0的时候都会执行完f()回调函数后将其值设置为1,保证了单实例。

Cond在满足一定条件时候唤醒goroutine

采用生产者消费者的问题来补充理解,当容量为1时候,规定了只能生产1和消费1,则在生产时候,消费只能执行等待,当生产完后则进行协程的唤醒,可以执行消费。在GO中使用的是Signal和Broadcast两个标签来执行等待的通知,唤醒goroutine。

package main

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

type Queue struct {
	queue []string
	cond  *sync.Cond
}

func main() {
	q := Queue{
		queue: []string{},
		cond:  sync.NewCond(&sync.Mutex{}),
	}
	go func() {
		for {
			q.Enqueue("a")
			time.Sleep(time.Second * 2)
		}
	}()
	for {
		q.Dequeue()
		time.Sleep(time.Second)
	}
}

func (q *Queue) Enqueue(item string) {
	q.cond.L.Lock()
	defer q.cond.L.Unlock()
	q.queue = append(q.queue, item)
	fmt.Printf("putting %s to queue, notify all\n", item)
	q.cond.Broadcast()
}

func (q *Queue) Dequeue() string {
	q.cond.L.Lock()
	defer q.cond.L.Unlock()
	for len(q.queue) == 0 {
		fmt.Println("no data available, wait")
		q.cond.Wait()
	}
	result := q.queue[0]
	q.queue = q.queue[1:]
	return result
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值