Go中同/异步与锁的应用~~sync包

news2024/11/26 20:38:01

Go中锁的实现~~sync包

go中sync包中提供了互斥锁;

在前面Go中channel文章中我们使用了time.Sleep()函数使得main函数的Goroutine阻塞至所有协程Goroutine结束,但这并不是一个很好的办法,因为我们实际应用中并不能准确知道协程什么时候结束(这里面要考虑服务器的性能,网络波动以及io等一系列因素;

sysn包中提供了WaitGroup来实现协程之间的协调;

同步等待组

同步的sync与异步的sync;

在go中提供了同步等待组WaitGroup
来看源码:

//等待一组Goroutine完成; 阻塞的直至所有的goroutine完成;
// 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.
//
// In the terminology of the Go memory model, a call to Done
// “synchronizes before” the return of any Wait call that it unblocks.
type WaitGroup struct {
	noCopy noCopy

	state atomic.Uint64 // high 32 bits are counter, low 32 bits are waiter count.
	sema  uint32
}

WaitGroup 实现了一些函数:

Add() 方法来设置应等待Goroutine的数量;

在结构体WaitGroup 中有这么一个属性:
state atomic.Uint64 //高32用于计数,低32为用于统计等待的数量

//如果
func (wg *WaitGroup) Add(delta int) {
	if race.Enabled {
		if delta < 0 { //判断增加的值
			// Synchronize decrements with Wait.
			race.ReleaseMerge(unsafe.Pointer(wg))
		}
		race.Disable()
		defer race.Enable()
	}
	state := wg.state.Add(uint64(delta) << 32)
	v := int32(state >> 32)
	w := uint32(state)
	if race.Enabled && delta > 0 && v == int32(delta) {
		// The first increment must be synchronized with Wait.
		// Need to model this as a read, because there can be
		// several concurrent wg.counter transitions from 0.
		race.Read(unsafe.Pointer(&wg.sema))
	}
	if v < 0 {
		panic("sync: negative WaitGroup counter")
	}
	if w != 0 && delta > 0 && v == int32(delta) {
		panic("sync: WaitGroup misuse: Add called concurrently with Wait")
	}
	if v > 0 || w == 0 {
		return
	}
	// This goroutine has set counter to 0 when waiters > 0.  //当等待的协程大于0,设置计数器>0
	// Now there can't be concurrent mutations of state:
	// - Adds must not happen concurrently with Wait,
	// - Wait does not increment waiters if it sees counter == 0.
	// Still do a cheap sanity check to detect WaitGroup misuse.
	if wg.state.Load() != state {
		panic("sync: WaitGroup misuse: Add called concurrently with Wait")
	}
	// Reset waiters count to 0.
	wg.state.Store(0)
	for ; w != 0; w-- {
		runtime_Semrelease(&wg.sema, false, 0)
	}
}

Add()方法中在结构以WaitGroup{}内部计数器上加上delta,delta可以是负数;如果计数器变为0,那么等待的所有groutine都会被释放;
如果计数器小于0,则会出发panic;

注意: Add()方法参数为正数时的调用应该在Wait()之前,否则如果等待的所有groutine都会被释放(或者没有被全部释放),那么可能只会等待很少的goroutine完成;

通常我们应该在创建新的Goroutine或者其他应该等待的事件之前调用;

结束时应该调用**Done()**方法

Done()用于减少WaitGroup计数器的值,应该在Goroutine的最后执行;

// Done decrements the WaitGroup counter by one.
func (wg *WaitGroup) Done() {
	wg.Add(-1)
}

Wait()方法阻塞Goroutine 直到WaitGroup计数减为0

// Wait blocks until the WaitGroup counter is zero.
func (wg *WaitGroup) Wait() {
	if race.Enabled {
		race.Disable()
	}
	for {
		state := wg.state.Load()
		v := int32(state >> 32) //低32位用于统计等待数
		w := uint32(state)
		
		if v == 0 { //如果等待数位0,那么释放所有的Goroutine
			// Counter is 0, no need to wait.
			if race.Enabled {
				race.Enable()
				race.Acquire(unsafe.Pointer(wg))
			}
			return
		}
		// Increment waiters count.
		if wg.state.CompareAndSwap(state, state+1) {  //CAS操作
			if race.Enabled && w == 0 {
				// Wait must be synchronized with the first Add.
				// Need to model this is as a write to race with the read in Add.
				// As a consequence, can do the write only for the first waiter,
				// otherwise concurrent Waits will race with each other.
				race.Write(unsafe.Pointer(&wg.sema))
			}
			runtime_Semacquire(&wg.sema)
			if wg.state.Load() != 0 {
				panic("sync: WaitGroup is reused before previous Wait has returned")
			}
			if race.Enabled {
				race.Enable()
				race.Acquire(unsafe.Pointer(wg))
			}
			return
		}
	}
}

一个代码Demo


func main() {
	var sy sync.WaitGroup
	fmt.Printf("%T\n", sy)
	fmt.Println(sy)
	//增加10个
	sy.Add(5)
	rand.Seed(time.Now().UnixNano())
	go WaitGroupTest(1, &sy)
	go WaitGroupTest(2, &sy)
	go WaitGroupTest(3, &sy)
	go WaitGroupTest(4, &sy)
	go WaitGroupTest(5, &sy)
	sy.Wait()
	defer fmt.Println("main exit")
}
func WaitGroupTest(num int, sy *sync.WaitGroup) {
	for i := 0; i < 5; i++ {
		fmt.Printf("第%d号子goroutine,%d \n", num, i)
		time.Sleep(time.Second)
		 
	}
	sy.Done()
}

当我们往WaitGroup中添加协程时要在定义协程之前
运行结果:
在这里插入图片描述

小结:
1,声明一个WaitGroup,
2,调用Add添加期望的计数
3,构建协程
4,等待所有协程运行完成后主协程才退出;

当我们注销掉sy.Done(),再次运行 会出现下面的结果–死锁了
在这里插入图片描述
当我们调整一下for循环中数量:
在这里插入图片描述
在这里插入图片描述

//只读 <-chan  只写的 chan <-

func ChanWaitGroup(ch chan int, sy *sync.WaitGroup) {
	for i := 0; i < 10; i++ {
		//通道放入数据
		ch <- i
	}
	defer close(ch) //要关闭通道
	defer sy.Done() //执行完毕要标记一下执行完毕计数-1
}

func main() {
	var wt sync.WaitGroup
	var ch chan int
	ch = make(chan int)
	wt.Add(3)
	//这里可以放入一些需要执行的函数,这些函数之间有关联,需要都执行完毕后在执行别的代码

	go ChanWaitGroup(ch, &wt)

	for i2 := range ch {
		fmt.Println(i2)
	}
	time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) //纯粹是为了复习
	go WaitGroupTest(1, &wt) //
	go WaitGroupTest(2, &wt) //
	wt.Wait()
	fmt.Println("main exit")
}

所有子Goroutine运行结束以后主Goroutine才退出。

互斥锁

在Go中互斥锁也是一个结构体:

// 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.
//
// In the terminology of the Go memory model,
// the n'th call to Unlock “synchronizes before” the m'th call to Lock
// for any n < m.
// A successful call to TryLock is equivalent to a call to Lock.
// A failed call to TryLock does not establish any “synchronizes before”
// relation at all.
type Mutex struct {
	state int32
	sema  uint32
}

Mutex是一个互斥锁,可以创建为其他结构体的字段;零值为解锁状态。Mutex类型的锁和Goroutine无关,可以由不同的Goroutine加锁和解锁。

再看一下 加锁与解锁的源码:

// Lock locks m.   锁住 m 
// If the lock is already in use, the calling goroutine blocks until the mutex is available.   //如果该对象被锁住了,那么就阻塞,直到m解锁
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()
}


// Unlock unlocks m. //解锁m 
// It is a run-time error if m is not locked on entry to Unlock.   如果m没有被锁住就会报error
// 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() {
	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)
	}
}

//尝试加锁
// TryLock tries to lock m and reports whether it succeeded. 
//
// Note that while correct uses of TryLock do exist, they are rare,
// and use of TryLock is often a sign of a deeper problem  in a particular use of mutexes. //很少用tryLock,而TryLock的使用通常表明在特定的互斥锁使用中存在更深层次的问题。
func (m *Mutex) TryLock() bool {
	old := m.state
	if old&(mutexLocked|mutexStarving) != 0 {
		return false
	}

	// There may be a goroutine waiting for the mutex, but we are
	// running now and can try to grab the mutex before that
	// goroutine wakes up.
	if !atomic.CompareAndSwapInt32(&m.state, old, old|mutexLocked) {
		return false
	}

	if race.Enabled {
		race.Acquire(unsafe.Pointer(m))
	}
	return true
}

同时我们根据源码可以直到,如果要对一个对象的方法加锁/解锁,可以在结构体中声明一个匿名对象,比如下面这样:

type ATR struct {
	sync.Mutex
}

func (a ATR) LockT() {

}

func main() {
	atr := ATR{}
	atr.Mutex.Lock()
	atr.LockT()
}

实际问题–售票,用go代码实现:

var tickets = 10
var wg sync.WaitGroup
var sm sync.Mutex

func saleTickets(winname string, swg *sync.WaitGroup) {
	for {
		//上锁
		sm.Lock()
		if tickets > 0 { //如果有票
			tickets-- //卖票
			time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)
		} else {
			fmt.Println(winname, "窗口---票售完")
			sm.Unlock()
			break
		}
		sm.Unlock()
	}

	defer swg.Done()
}

func main() {

	wg.Add(10)
	for i := 0; i < 10; i++ {
		go saleTickets("火车站", &wg)
	}

	wg.Wait()
}

实际就是加锁解锁方法的应用;

读写锁

在java中有关于读写锁的一些类和方法,在go中也有读写锁的一些API;

首先我们来看一下

未完待续

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/598501.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

日志脱敏之后,无法根据信息快速定位怎么办?

日志脱敏之殇 小明同学在一家金融公司上班&#xff0c;为了满足安全监管要求&#xff0c;最近天天忙着做日志脱敏。 无意间看到了一篇文章金融用户敏感数据如何优雅地实现脱敏&#xff1f; 感觉写的不错&#xff0c;用起来也很方便。 不过日志脱敏之后&#xff0c;新的问题就…

将Parquet文件的数据导入Hive 、JSON文件导入ES

文章目录 将Parquet文件的数据导入Hive查询parquet文件格式编译cli工具查看元数据信息查询抽样数据 创建hive表 数据存储格式采用parquet加载文件 将json数据导入ESES批量导入api原始json文件内容索引结构重组json脚本重组后的json文件bulk api调用 将Parquet文件的数据导入Hiv…

Spring Boot如何实现分布式文件系统

Spring Boot如何实现分布式文件系统 随着数据量的不断增长&#xff0c;单机文件系统已经无法满足大规模数据存储和访问的需求&#xff0c;因此分布式文件系统变得越来越重要。本文将介绍如何使用 Spring Boot 实现分布式文件系统。 1. 分布式文件系统的设计 分布式文件系统是…

【JavaSE】Java基础语法(四十):UDP通信程序

文章目录 1. UDP发送数据2. UDP接收数据【应用】3. UDP通信程序练习【应用】4. UDP三种通讯方式 1. UDP发送数据 Java中的UDP通信 UDP协议是一种不可靠的网络协议&#xff0c;它在通信的两端各建立一个Socket对象&#xff0c;但是这两个 Socket只是发送&#xff0c;接收数据的对…

Doris的一些进阶用法

6.doris进阶 6.1修改表 6.1.1修改表名 示例&#xff1a; 将名为 table1 的表修改为 table2 SQLALTER TABLE table1 RENAME table2; -- 示例 ALTER TABLE aggregate_test RENAME aggregate_test1; 将表 example_table 中名为 rollup1 的 rollup index 修改为 rollup2 SQLA…

V2board 1.6.1 提权漏洞(web缓存投毒)

目录 复现环境&#xff1a; 漏洞产生的原因&#xff1a; 漏洞的利用以及复现&#xff1a; 复现环境&#xff1a; 在gethub中直接拉去docker镜像 vulhub/README.zh-cn.md at master vulhub/vulhub GitHub 漏洞产生的原因&#xff1a; 前端的认证方式与后端并没有进行区分…

【LeetCode热题100】打卡第9天:电话号码的字母组合

文章目录 电话号码的字母组合⛅前言&#x1f512;题目&#x1f511;题解 电话号码的字母组合 ⛅前言 大家好&#xff0c;我是知识汲取者&#xff0c;欢迎来到我的LeetCode热题100刷题专栏&#xff01; 精选 100 道力扣&#xff08;LeetCode&#xff09;上最热门的题目&#xf…

本地运行 LLAMA GPT-3.5-TURBO开源项目

git&#xff1a; nomic-ai/gpt4all: gpt4all: an ecosystem of open-source chatbots trained on a massive collections of clean assistant data including code, stories and dialogue (github.com) 下载好源码后&#xff0c;的目录结构&#xff1a; 视频中说的 chat 目录…

视图和用户管理

目录 视图基本使用视图规则和限制 用户管理用户用户信息创建用户删除用户修改用户密码 数据库的权限给用户授权回收权限 视图 视图是一个虚拟表&#xff0c;其内容由查询定义。同真实的表一样&#xff0c;视图包含一系列带有名称的列和行数据。视图的数据变化会影响到基表&…

基于Python的接口自动化-构建mock接口服务

引言 Mock 即模拟&#xff0c;就是在测试过程中&#xff0c;对于某些不容易构造或者不容易获取的对象&#xff0c;用一个虚拟的对象来创建以便测试的测试方法&#xff0c;其最大的优势就是降级前后端耦合度&#xff0c; 使前端工程师可以不依赖后端返回数据&#xff0c;先开发前…

堆排序及top k 问题

目录 一&#xff1a;堆排序 1.向上调整建堆 2.向下调整建堆 3.向上调整建堆时间复杂度 4.向下调整建堆时间复杂度 二&#xff1a;找 top k 问题 1.造数据 2.进行建堆&#xff0c;查找最大的K个数据 一&#xff1a;堆排序 升序 --- 建大堆 --- 每个父亲节点 > 孩子节…

高德API JS 高德地图获取多个坐标点的中心点

高德API JS 高德地图获取多个坐标点的中心点 一、需求 我需要在地图上展示多个地点&#xff0c;并且展示的同时&#xff0c;地图缩放到合适的大小&#xff0c;要求刚好能显示全部点位&#xff0c;并且边缘留有一部分间隔。 做成如图所示这样。 二、需要用到的 AMap 类库 经…

使用Python绘制6.1儿童节消消乐,素描图,词云图,字符画图,提取轮廓图及蒙太奇效果图

这篇博客将介绍如何使用Python绘制6.1儿童节消消乐&#xff0c;素描图&#xff0c;词云图&#xff0c;字符画图&#xff0c;提取轮廓图及蒙太奇效果图。 使用Python绘制端午dragboat消消乐 美轮美奂的界面效果 1. 效果图 6.1儿童节快乐原始图VS素描图&#xff1a; 素描进阶…

内网穿透-公网ip-方法总结-访问内网服务器-frp-虚拟服务器

文章目录 1.固定IP2.虚拟服务器转发3.IP盒子4.总结 1.固定IP 第一种方式是向三大电信服务商购买专用通道&#xff0c;固定IP&#xff0c;这种方式是最正统&#xff0c;也是各大虚拟服务器服务商采用的方式&#xff0c;宽带带宽有稳定的保障。在访问量不足的前提下&#xff0c;…

anaconda 安装_Linux系统上

安装流程 1 下载安装包 官网 https://www.anaconda.com/download#downloads 2 执行安装 bash Anaconda3-2021.11-Linux-x86_64.sh3 安装过程 一路enteryes&#xff0c;接受licence、指定安装路径和init之后安装完成。 检验anaconda是否安装成功。 conda --version或 con…

【LeetCode】12,整数转罗马数字。 难度等级:中等。易错点:使用 python 字典构建哈希表时要考虑哈希表是否有序

文章目录 一、题目二、我的解法&#xff1a;基于有序哈希表的贪心算法2.1 使用 dict 构建哈希表2.2 使用两个 list / tuple 构建有序哈希表 一、题目 二、我的解法&#xff1a;基于有序哈希表的贪心算法 2.1 使用 dict 构建哈希表 贪心法则&#xff1a;我们每次尽量使用最大的…

基于AT89C52单片机的多功能万年历设计

点击链接获取Keil源码与Project Backups仿真图&#xff1a; https://download.csdn.net/download/qq_64505944/87853675 源码获取 目 录 摘 要 1 1 方案论证 2 1.1 单片机芯片的选择方案和论证 2 1.2 显示模块选择方案和论证 2 1.3 时钟芯片的选择方案和论证 3 1.4 电路设计…

BitLocker加密卷“恢复密钥(数字密码)”提取还原

BitLocker是微软Windows自带的用于加密磁盘分卷的技术。 通常&#xff0c;解开后的加密卷通过Windows自带的命令工具“manage-bde”可以查看其恢复密钥串&#xff0c;如下图所示&#xff1a; 如图&#xff0c;这里的数字密码下面的一长串字符串即是下面要提取恢复密钥。 在计…

IMA/EVM完整性检测代码分析

IMA/EVM完整性检测 IMA&#xff08;Integrity Measurement Architecture&#xff09;是一个内核安全子系统&#xff0c;用于检测文件或数据的完整性和安全性。IMA的hook机制指的是内核接口钩子&#xff08;kernel interface hooks&#xff09;&#xff0c;用于向IMA注册和实现…

第三章 部署Web及WDS服务

♥️作者介绍&#xff1a;奇妙的大歪 ♥️个人名言&#xff1a;但行前路&#xff0c;不负韶华&#xff01; ♥️个人简介&#xff1a;云计算网络运维专业人员 目录 一.什么是web 1.www(world wide web)万维网 世界 维度 2.www服务软件 3.info…