Go 的 string 是否线程安全

1,317 阅读1分钟

Go 的 string 是否线程安全

string是 Go 的内建类型,我们知道string的值是不可变的,但string变量不是。如果多个 goroutine 同时修改同一个string变量,需要添加线程安全机制,例如锁或者原子操作。

string的值是不可变的

func TestModifyString(t *testing.T) {
	var s string = "abc"
	s[0] = '0' // Cannot assign to s[0]
}

执行这个测试得到的结果:

cannot assign to s[0] (value of type byte)

string变量是可变的

func TestString2(t *testing.T) {
	a := "hello"
	a = "world"
	fmt.Println(a)
}

执行这个测试得到的结果:

world

string线程不安全

string底层是一个struct类型,包含一个字符串描述符str和一个长度len

runtime/string.go

type stringStruct struct {
    str unsafe.Pointer
    len int
}

下面这个测试可以检测string是否线程安全:

func TestString(t *testing.T) {
	ch := make(chan string)
	a := "1"
	go func() {
		i := 0
		for {
			if i%2 == 0 {
				a = "1"
			} else {
				a = "22"
			}
			time.Sleep(time.Millisecond * 1) // 阻止编译器优化
			i++
		}
	}()

	go func() {
		for {
			b := a
			if b != "1" && b != "22" {
				ch <- b
			}
		}
	}()

	for i := 0; i < 10; i++ {
		fmt.Println("Got string: ", <-ch)
	}
}

执行这个测试得到的结果:

Got string:  2
Got string:  2
Got string:  15
Got string:  2
Got string:  2
Got string:  15
Got string:  15
Got string:  15
Got string:  2
Got string:  2

可以看到在频繁的写入操作中,另一协程可能读到部分写入的结果跟预想的不一致。(len为1,指针指向22,或者是len为2,指针指向了1)。

在并发场景下,stringinterface一样,都是需要使用atomic包来保证读写的原子性。

参考

stackoverflow.com/questions/5…