Go-unsafe

本文探讨了Go语言中不安全编程的实践,特别是如何安全地使用`unsafe.Pointer()`进行类型转换,原子操作在并发中的应用,以及在与C库交互中的注意事项。通过实例展示了不当使用的风险和正确用法,总结了关键点和适用场景。
摘要由CSDN通过智能技术生成

目录

一、不安全编程

1、使用场景

2、不安全行为的危险性

3、合理的使用 unsafe.Pointer()

4、Atomic原子类型操作

二、总结


一、不安全编程

1、使用场景

主要是和外部的C程序库进行交互时,其它场景使用较少。

2、不安全行为的危险性

虽然 Go语言不支持强制类型转换,但是一旦我们使用不安全指针 unsafe.Pointer() 把它的地址拿出来之后,我们就可以把它转换为任何类型的指针。

package unsafe_programming

import (
	"testing"
	"unsafe"
)

//用 unsafe.Pointer() 做数据类型强制转换
func TestUnsafe(t *testing.T)  {
	var num int = 10

	numAddr := unsafe.Pointer(&num)
	t.Log(numAddr)

	//用不安全指针拿到 num 的地址,并转换为 float64 类型
	f := *(*float64)(unsafe.Pointer(&num))

	t.Logf("type: %T, value: %v", f, f)
}

/*
=== RUN   TestUnsafe
unsafe_test.go:12: 0xc00001e2a8
unsafe_test.go:16: type: float64, value: 5e-323
--- PASS: TestUnsafe (0.00s)
PASS
*/

通过上面的代码我们可以看出,我们原本想将 整数 10 转换为 64 位浮点数,但是最终的结果却是 5e-323 ,与我们预期的不符,所以在使用 unsafe 时一定要特别注意,这种转换是非常非常危险的。

3、合理的使用 unsafe.Pointer()

当然,在某些情况下我会还是可以考虑用 unsafe.Pointer() 的,比如下面这种数据类型相同的场景。

package unsafe_programming

import (
	"testing"
	"unsafe"
)

//自定义一个数据类型
type MyInt int

//相同数据类型之间可以做数据类型转换
func TestConvert(t *testing.T) {
	s1 := []int{1, 2, 3, 4}
	s2 := *(*[]MyInt)(unsafe.Pointer(&s1))

	t.Logf("type: %T, value: %v", s2, s2)
}
/*
=== RUN   TestConvert
unsafe_test.go:37: type: []unsafe_programming.MyInt, value: [1 2 3 4]
--- PASS: TestConvert (0.00s)
PASS
*/

4、Atomic原子类型操作

atomic 提供了一个对指针的原子操作,这个指针的原子操作经常会在读写一个并发缓存时用到,为了达到读和写的线程安全,我们在写的时候先写到一个 共享的buffer 里面,当我们写完后,我们在用一个原子操作,把读的位置重新指向我们新写好的那个buffer的地址。那么这个切换的过程就需要用 atomic 来保证线程安全。 

package unsafe_programming

import (
	"fmt"
	"sync"
	"sync/atomic"
	"testing"
	"unsafe"
)

//atomic原子类型操作
func TestAtomic(t *testing.T) {
	var shareBufPtr unsafe.Pointer
	writeDataFn := func() {
		data := []int{}
		for i := 0; i < 10; i++ {
			data = append(data, i)
		}
		//数据写完后,用一个原子操作,把它放入共享的 buffer
		atomic.StorePointer(&shareBufPtr, unsafe.Pointer(&data))
	}

	readDataFun := func() {
		//用原子操作,去共享 buffer 里面取数据
		data := atomic.LoadPointer(&shareBufPtr)
		fmt.Println(data, *(*[]int)(data))
	}

	var wg sync.WaitGroup
	//因为下面是协程并发读取数据,所有需要先写点儿数据进去,
	//否则会报错: invalid memory address or nil pointer dereference
	writeDataFn()

	//分别开5条写协程和5条读协程,看数据是否能正常读写
	for j := 0; j < 5; j++ {
		wg.Add(1)
		go func() {
			writeDataFn()
			wg.Done()
		}()

		wg.Add(1)
		go func() {
			readDataFun()
			wg.Done()
		}()
	}

	wg.Wait()
}
/*
=== RUN   TestAtomic
0xc000130030 [0 1 2 3 4 5 6 7 8 9]
0xc000130030 [0 1 2 3 4 5 6 7 8 9]
0xc000130078 [0 1 2 3 4 5 6 7 8 9]
0xc00008e000 [0 1 2 3 4 5 6 7 8 9]
0xc00008e030 [0 1 2 3 4 5 6 7 8 9]
--- PASS: TestAtomic (0.00s)
PASS
*/

我们可以看出,经过 atomic 进行原子操作后,多协程最终的读写结果都正常。

二、总结

  • unsafe主要用在外部的C程序库进行交互
  • 不安全指针 unsafe.Pointer() 把它的地址拿出来之后,我们就可以把它转换为任何类型的指针,但是要注意这种转换是非常非常危险,只能用在同类型间,不同类型直接不要用。
  • atomic.StorePointer() 用来将数据存放到一个 unsafe 指针变量里面。
  • atomic.LoadPointer() 用来从 unsafe 指针变量里面读取数据。

:这篇博文是我学习中的总结,如有转载请注明出处:

https://blog.csdn.net/DaiChuanrong/article/details/118771036

上一篇Go-反射编程

下一篇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值