导读:本期聚焦于小伙伴创作的《如何在Golang中使用指针进行并发安全访问_结合sync包示例》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《如何在Golang中使用指针进行并发安全访问_结合sync包示例》有用,将其分享出去将是对创作者最好的鼓励。

在Golang的并发编程场景中,多个goroutine同时操作同一个指针指向的共享数据是常见需求,但如果缺乏正确的同步机制,就会引发数据竞争,导致程序出现不可预期的错误。指针本身存储的是内存地址,多个goroutine拿到同一个指针后,会直接操作同一块内存区域,此时如果没有同步控制,读写操作会相互干扰。

如何在Golang中使用指针进行并发安全访问_结合sync包示例

指针与并发访问的基础概念

指针是Golang中用于存储变量内存地址的特殊类型,通过指针可以直接修改对应内存地址中的值。而并发安全指的是当多个goroutine同时访问某个资源时,无论访问的顺序如何,程序都能得到正确的结果,不会出现数据损坏或者逻辑错误的情况。

当多个goroutine同时持有同一个指针,并且对该指针指向的数据进行写操作时,就会产生数据竞争。比如两个goroutine同时对一个指针指向的整型变量做加1操作,最终的结果可能比预期的小,因为读和写的操作不是原子的。

未加同步的指针并发访问问题示例

下面是一段没有做任何同步处理的并发访问指针的示例代码,我们可以观察它的运行结果:

package main

import (
	"fmt"
	"time"
)

// 定义一个存储计数的结构体
type Counter struct {
	val int
}

func main() {
	// 创建结构体实例,获取指针
	c := &Counter{val: 0}
	// 启动10个goroutine同时修改指针指向的值
	for i := 0; i < 10; i++ {
		go func() {
			// 每个goroutine对值加1000次
			for j := 0; j < 1000; j++ {
				c.val++
			}
		}()
	}
	// 等待所有goroutine执行完成
	time.Sleep(time.Second * 2)
	// 打印最终结果,预期是10000
	fmt.Println("最终计数结果:", c.val)
}

运行这段代码多次,你会发现最终的结果往往小于10000,这就是因为多个goroutine同时修改c.val时发生了数据竞争,部分加操作被覆盖了。

使用sync.Mutex实现指针并发安全访问

sync包中的Mutex是最常用的互斥锁,它可以保证同一时间只有一个goroutine能够访问被保护的资源,从而实现并发安全。我们可以在结构体内部嵌入Mutex,对指针指向的数据的修改操作加锁。

修改后的安全版本代码如下:

package main

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

// 定义带互斥锁的计数器结构体
type SafeCounter struct {
	mu  sync.Mutex
	val int
}

func main() {
	// 获取结构体指针
	c := &SafeCounter{val: 0}
	// 启动10个goroutine并发修改
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for j := 0; j < 1000; j++ {
				// 加锁,保证同一时间只有一个goroutine能修改val
				c.mu.Lock()
				c.val++
				// 修改完成后解锁
				c.mu.Unlock()
			}
		}()
	}
	// 等待所有goroutine完成
	wg.Wait()
	// 打印结果,此时结果稳定为10000
	fmt.Println("最终计数结果:", c.val)
}

这里通过c.mu.Lock()c.mu.Unlock()把修改val的操作包裹起来,同一时间只有一个goroutine能执行这段逻辑,避免了数据竞争。

使用sync.RWMutex优化读多写少场景

如果我们的场景是读操作远多于写操作,使用Mutex会导致所有读操作也串行执行,效率比较低。这时候可以使用RWMutex,它支持读写锁分离,多个读操作可以同时获取读锁,只有写操作需要获取写锁,写锁和读锁是互斥的。

示例代码如下:

package main

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

// 定义带读写锁的计数器结构体
type RWSafeCounter struct {
	mu  sync.RWMutex
	val int
}

// 读取值的方法
func (c *RWSafeCounter) Get() int {
	// 获取读锁,多个goroutine可以同时获取读锁
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.val
}

// 修改值的方法
func (c *RWSafeCounter) Add(n int) {
	// 获取写锁,写锁会阻塞其他读锁和写锁
	c.mu.Lock()
	defer c.mu.Unlock()
	c.val += n
}

func main() {
	c := &RWSafeCounter{val: 0}
	var wg sync.WaitGroup
	// 启动5个写goroutine
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for j := 0; j < 1000; j++ {
				c.Add(1)
			}
		}()
	}
	// 启动10个读goroutine
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for j := 0; j < 100; j++ {
				fmt.Println("当前读取值:", c.Get())
			}
		}()
	}
	wg.Wait()
	fmt.Println("最终计数结果:", c.Get())
}

这种场景下,读操作可以并发执行,只有写操作会阻塞其他操作,整体性能比使用Mutex更高。

其他sync包工具的适用场景

除了Mutex和RWMutex,sync包还提供了其他工具可以配合指针实现并发安全:

  • sync.WaitGroup:用于等待一组goroutine执行完成,避免主goroutine提前退出,上面的示例中已经用到。
  • sync.Once:保证某个操作在并发场景下只执行一次,比如指针指向的资源的初始化操作,适合只需要执行一次的初始化逻辑。
  • sync.Map:如果指针指向的是map类型的数据,直接使用map加锁也可以,但sync.Map是官方提供的并发安全的map实现,更适合并发场景下的map操作。

注意事项

在使用指针和sync包实现并发安全时,需要注意以下几点:

  • 锁的粒度要合适,不要过大也不要过小,过大会影响并发性能,过小可能导致部分逻辑没有被保护到。
  • 不要忘记解锁,最好使用defer语句来解锁,避免程序出现异常时锁没有被释放,导致死锁。
  • 不要对同一个指针指向的数据混用不同的同步机制,比如一部分操作加Mutex,另一部分操作加RWMutex,会导致同步失效。
  • 指针传递时要注意,不要将未加同步的指针随意传递给多个goroutine,尽量把同步逻辑封装在结构体内部,对外提供安全的方法。

通过合理结合指针和sync包的工具,我们可以在Golang的并发场景中安全地操作共享数据,避免数据竞争带来的问题,保证程序的正确性和稳定性。

Golang指针并发安全sync包goroutine修改时间:2026-07-14 22:12:35

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。