从一个警告想到的关于锁的小问题

文章讲述了在Go语言中,使用golangci-lint检查代码时发现的一个关于结构体中包含sync.Mutex锁的问题。当结构体被复制时,锁也会被复制,这可能导致并发安全问题,因为不同的副本持有不同的锁,破坏了互斥访问的原意。正确的做法是传递锁的指针,确保所有协程使用同一把锁。通过一个示例程序说明了返回结构体指针和返回结构体副本两种情况的并发行为差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景:今天心血来潮用 golangci-lint 检查自己的代码,发现了一个误用kitex中结构体的警告

internal/user/dao/user.go:45:11: copylocks: return copies lock value: TTMS/kitex_gen/user.User contains 
google.golang.org/protobuf/internal/impl.MessageState contains sync.Mutex (govet)
			return u, nil

第一反应是赶紧看怎么解决,结果发现返回一个指针类型就没有警告了,后来仔细想了一下,发现确实是个之前没注意到的小问题,但关键时刻也可能念成大祸!

如果结构体中的锁被复制了(即返回的结构体是原始结构体的副本),则在使用该结构体时会出现问题。这是因为 sync.Mutex 锁是一个互斥量,它的主要作用是用于多个 goroutine 对共享资源进行互斥访问。如果一个结构体的副本中包含一个锁的副本,则在使用该结构体的时候,可能会导致两个 goroutine 同时访问同一个共享资源,从而破坏了锁的互斥性。

一句话总结就是:一个锁的复制出来的副本和原来是不一样的,但是把锁对象的指针传出去,所有能引用该指针的对象拿到的都是同一把锁。

比如,以下示例:


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

type MyStruct struct {
	mutex sync.Mutex
	data  int
}

// 1.返回 *MyStruct 时,s1 s2 用的是同一把锁
func getMyStruct() *MyStruct {
	return &MyStruct{data: 0}
}

// 2.返回 MyStruct 时,s2 用的是 s1中锁的副本,本质上是两把锁
//
//	func getMyStruct() MyStruct {
//		return MyStruct{data: 0}
//	}
func main() {
	s1 := getMyStruct()
	s2 := s1
	go func() {
		for i := 0; i < 10000; i++ {
			s1.mutex.Lock()
			s1.data++
			s1.mutex.Unlock()
		}
	}()

	go func() {
		for i := 0; i < 10000; i++ {
			s2.mutex.Lock()
			s1.data++
			s2.mutex.Unlock()
		}
	}()

	// 等待 goroutine 结束
	time.Sleep(1 * time.Second)

	fmt.Println(s1.data)
	// 1.当两个协程使用同一把锁时 结果为 20000
	// 2.当两个协程使用不同锁时(相当直接没加锁) 结果一般不足 20000
}

 在上面的示例中,我们 s1 传给 s2,并在两个 goroutine 中对它们进行修改。

传递锁的指针和锁的副本两种情况分别对应 1,2 两种处理。

<think>嗯,用户问的是在Vue3中如何实现弹窗出现时阻止鼠标滚动等行为。我需要先回忆一下Vue3的相关知识,然后想想阻止滚动的常见方法。 首先,弹窗出现时通常需要禁止背景页面滚动,避免用户误操作。这时候可能需要操作DOM的样式,比如设置overflow为hidden。不过直接操作document.body.style可能会影响全局,需要注意恢复原状。 然后,用户提到的是Vue3,所以应该用组合式API的写法,比如使用setup函数或者script setup语法。可能需要用ref来控制弹窗的显示状态,然后在显示的时候添加阻止滚动的逻辑,隐藏的时候移除。 另外,除了CSS的方法,还可以考虑事件监听。比如在弹窗显示时阻止wheel和touchmove事件,这样在移动端也能生效。但要注意,事件监听可能需要被动事件处理,或者使用passive: false来确保preventDefault生效,不过可能会有性能问题,特别是移动端。 还需要考虑多个弹窗的情况,比如同时出现多个弹窗时,应该在最后一个弹窗关闭后才恢复滚动,避免其中一个关闭后就解除定。这时候可能需要一个计数器或者全局状态管理。 然后想到,可能需要在弹窗组件挂载的时候添加阻止滚动的逻辑,卸载的时候移除。这时候可以用onMounted和onBeforeUnmount生命周期钩子。比如在onMounted里给body添加overflow: hidden,并在卸载时恢复。 但直接修改body的overflow可能会有副作用,比如页面原本有滚动条的话,突然隐藏会导致布局跳动。这时候可以考虑给html和body同时设置overflow: hidden,或者在弹窗出现时给body添加一个固定定位,并设置top为当前滚动位置,然后动态计算宽度,避免布局变化。不过这可能比较复杂。 另外,使用CSS的body { position: fixed; width: 100%; top: -scrollTop }这种方法也可以,但需要记录当前的滚动位置,关闭弹窗时恢复。这需要用到document.documentElement.scrollTop或者window.pageYOffset,然后在Vue中用ref保存这个值。 再考虑事件监听的方法,比如在弹窗显示时添加一个事件监听器,当用户滚动时阻止默认行为。但要注意,passive事件监听器不能和preventDefault一起使用,所以可能需要设置passive为false,但这可能在移动端导致性能问题,特别是Chrome会警告。 所以,可能的最佳实践是结合CSS和事件监听两种方法。比如,在弹窗显示时设置body的overflow为hidden,并且添加touchmove和wheel事件的阻止,这样在移动端和桌面端都能生效。但要注意移除事件监听器,避免内存泄漏。 在Vue3中,可以使用watch来监听弹窗的显示状态,当状态变为true时执行定逻辑,false时解。或者直接在弹窗组件的挂载和卸载生命周期中处理。 另外,可能需要注意SSR的情况,比如在服务端渲染时document不存在,所以需要判断是否在客户端环境,可以使用onMounted来确保代码在客户端执行。 总结下来,步骤应该是: 1. 当弹窗显示时,给body添加overflow: hidden样式,阻止滚动条出现。 2. 同时添加事件监听器,阻止wheel和touchmove的默认行为。 3. 在弹窗关闭时,移除样式和事件监听器。 4. 处理可能的布局跳动问题,比如记录滚动位置并应用固定定位。 然后,代码示例方面,可以用一个组合式函数,比如useScrollLock,返回定和解的方法。在弹窗组件中引入,并在显示时调用定,关闭时调用解。 还需要考虑多个弹窗同时存在的情况,比如用计数器来确保所有弹窗关闭后才恢复滚动。这时候可以用一个全局的变量或者使用Vue的provide/inject,或者Pinia这样的状态管理库。 不过对于简单的场景,可能不需要这么复杂,直接使用CSS的overflow隐藏即可。但用户可能希望更全面的解决方案,包括移动端的处理,所以需要给出两种方法的示例:CSS和事件监听,或者两者的结合。 最后,确保代码正确,比如在Vue3中使用ref、onMounted、onBeforeUnmount等API,样式修改时注意添加和移除的正确性,避免内存泄漏或样式残留。</think>在 Vue3 中实现弹窗时阻止页面滚动,可以通过以下分步方案实现: ### 一、核心方案分析 需要处理两种场景: 1. **桌面端**:阻止鼠标滚轮滚动 2. **移动端**:阻止触摸滑动 ### 二、完整实现方案 ```vue <script setup> import { ref, onMounted, onBeforeUnmount } from 'vue'; const isModalOpen = ref(false); let scrollPosition = 0; // 定滚动 const lockScroll = () => { // 记录当前滚动位置 scrollPosition = window.pageYOffset; // 桌面端方案 document.body.style.overflow = 'hidden'; // 移动端方案 document.addEventListener('touchmove', preventTouchScroll, { passive: false }); document.addEventListener('wheel', preventWheelScroll, { passive: false }); // 防止布局跳动(关键代码) document.body.style.position = 'fixed'; document.body.style.top = `-${scrollPosition}px`; document.body.style.width = '100%'; }; // 解除定 const unlockScroll = () => { // 恢复桌面端样式 document.body.style.overflow = ''; // 移除移动端事件 document.removeEventListener('touchmove', preventTouchScroll); document.removeEventListener('wheel', preventWheelScroll); // 恢复原始布局 document.body.style.position = ''; document.body.style.top = ''; document.body.style.width = ''; window.scrollTo(0, scrollPosition); }; // 事件处理器 const preventTouchScroll = (e) => { e.preventDefault(); }; const preventWheelScroll = (e) => { e.preventDefault(); }; // 控制弹窗 const toggleModal = () => { isModalOpen.value = !isModalOpen.value; if (isModalOpen.value) { lockScroll(); } else { unlockScroll(); } }; // 组件卸载时自动解除定 onBeforeUnmount(() => { if (isModalOpen.value) unlockScroll(); }); </script> <template> <button @click="toggleModal">打开弹窗</button> <div v-if="isModalOpen" class="modal"> <!-- 弹窗内容 --> <button @click="toggleModal">关闭弹窗</button> </div> </template> <style> .modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; z-index: 1000; } body { /* 保证初始状态正确 */ overflow: auto; position: static; } </style> ``` ### 三、关键点说明 1. **滚动位置记录** ```js scrollPosition = window.pageYOffset ``` 在定前保存当前滚动位置,用于关闭弹窗时恢复 2. **双重防护机制** - CSS方案:`overflow: hidden` 快速隐藏滚动条 - JS事件拦截:阻止默认滚动行为 3. **布局防抖动** ```js document.body.style.position = 'fixed'; document.body.style.top = `-${scrollPosition}px`; ``` 防止内容突然居中导致的布局跳动 4. **移动端特殊处理** ```js { passive: false } ``` 必须显式声明才能阻止默认行为 ### 四、进阶优化建议 1. **滚动穿透处理**:当弹窗内容需要滚动时,可添加以下逻辑: ```js const handleModalScroll = (e) => { const { scrollTop, scrollHeight, clientHeight } = e.target; if (scrollTop === 0) { e.target.scrollTop = 1; } else if (scrollTop + clientHeight === scrollHeight) { e.target.scrollTop = scrollHeight - clientHeight - 1; } }; ``` 2. **多弹窗管理**:使用计数器实现嵌套控制 ```js let lockCount = 0; const lockScroll = () => { if (lockCount === 0) { // 执行定逻辑 } lockCount++; }; ``` 3. **TypeScript 支持**:添加类型声明 ```ts interface ScrollLock { lockScroll: () => void; unlockScroll: () => void; } ``` ### 五、注意事项 1. **性能优化**:避免在滚动监听中执行复杂操作 2. **浏览器兼容性**:测试不同浏览器对 `passive` 事件的支持 3. **框架集成**:推荐封装为自定义指令或组合式函数复用 通过这种实现方式,可以确保弹窗交互的完整性和用户体验的一致性,同时保持代码的可维护性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

倚风听雨.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值