代码之家  ›  专栏  ›  技术社区  ›  RudziankoÅ­

字符串不可变与并发性

  •  7
  • RudziankoÅ­  · 技术社区  · 6 年前

    我们应该在写字符串时同步吗?因为字符串是不可变的,所以从两个不同的线程中读写之间永远不会出现不一致的状态,对吗?

    换句话说,为什么我们没有 atomic 对于字符串类型?

    3 回复  |  直到 6 年前
        1
  •  10
  •   icza    6 年前

    string 值是不可变的,但变量不是。变量就是它们的名字 变量 ,它们的值可以更改。

    您不需要同步来访问 一串 值,不能更改。如果A 一串 价值传递给你,即 一串 )始终保持不变(包的使用 unsafe 不算数)。

    当要访问的变量 一串 如果至少有一个访问是一个写操作(一个更改 一串 变量)。这对于go中任何类型的变量都是正确的, 一串 类型在任何方面都不是特殊的。

    这在实践中意味着什么?

    如果您有一个函数接收 一串 价值 "hello" ,您可以确定 一串 价值将保持不变 “你好” 不管怎样。因此,如果不自己更改参数(例如不为其指定新值),它将始终保持 一串 价值 “你好” .

    作为计数器示例,如果函数接收到一个切片值 []byte{1, 2, 3} 你没有同样的保证,因为切片是可变的。调用方还具有切片值(切片头),否则它无法首先传递它。如果调用者修改 元素 同时的切片,因为它们共享同一个支持数组,所以被传递给您的切片也将看到更改的数据…使用适当的同步;因为没有同步,这将是一个数据争用(因此是未定义的行为)。

    请参见此示例:

    var sig = make(chan int)
    
    func main() {
        s := []byte{1, 2, 3}
        go func() {
            <-sig
            s[0] = 100
            sig <- 0
        }()
        sliceTest(s)
    }
    
    func sliceTest(s []byte) {
        fmt.Println("First  s =", s)
    
        sig <- 0 // send signal to modify now
        <-sig    // Wait for modification to complete
    
        fmt.Println("Second s =", s)
    }
    

    输出(在 Go Playground ):

    First  s = [1 2 3]
    Second s = [100 2 3]
    

    专注于 sliceTest() :它接收一个切片,然后打印出来。然后稍等一点(给一个并发的goroutine一个“go”来修改它,并等待这个修改完成),然后再次打印它,它已经改变了,但是 切片测试() 它本身并没有修改它。

    现在如果 切片测试() 会收到一个 一串 相反,这是不可能发生的。

    见相关/可能的副本: Immutable string and pointer address

        2
  •  3
  •   Uvelichitel    6 年前

    在语言语法级别或标准库中对字符串类型定义的所有函数都返回一个新的字符串实例。没有一个函数在适当的位置改变字符串。只要遵循这个实践,您就可以同时获得安全。

        3
  •  1
  •   peterSO    6 年前

    我们应该在写字符串时同步吗?因为字符串是不变的,所以我们 从2中读取和写入的状态永远不会不一致 不同的线,对吗?

    这就是问题所在。答案是:写字符串时同步。很明显 string 变量是可变的,并且 一串 正如我前面已经解释过的,内容是不可变的。重申:


    go编译器将强制 一串 内容。例如,

    package main
    
    func main() {
        var s string = "abc"
        s[0] = '0'
    }
    

    输出:

    5:7: cannot assign to s[0]
    

    Go运行时 Data Race Detector 标记可变的不一致状态 一串 从不同的goroutine更新的变量。例如,写入 一串 变量,

    package main
    
    import "time"
    
    var s string = "abc"
    
    func main() {
        go func() {
            for {
                s = "abc"
            }
        }()
        go func() {
            for {
                s = "abc"
            }
        }()
        time.Sleep(1 * time.Second)
    }
    

    输出:

    $ go run -race racer.go
    ==================
    WARNING: DATA RACE
    Write at 0x00000050d360 by goroutine 6:
      main.main.func2()
          /home/peter/src/racer.go:15 +0x3a
    
    Previous write at 0x00000050d360 by goroutine 5:
      main.main.func1()
          /home/peter/src/racer.go:10 +0x3a
    
    Goroutine 6 (running) created at:
      main.main()
          /home/peter/src/racer.go:13 +0x5a
    
    Goroutine 5 (running) created at:
      main.main()
          /home/peter/src/racer.go:8 +0x42
    ==================
    Found 1 data race(s)
    exit status 66
    $
    

    原岗位:


    The Go Programming Language Specification

    String types

    字符串类型表示一组字符串值。字符串值是 (可能为空)字节序列。字符串是不可变的:一次 创建时,无法更改字符串的内容。


    一串 变量不可变。它包含一个字符串描述符, struct .

    type stringStruct struct {
        str unsafe.Pointer
        len int
    }
    

    例如,

    package main
    
    import "fmt"
    
    func main() {
        s := "abc"
        fmt.Println(s)
        s = "xyz"
        fmt.Println(s)
    
    }
    

    输出:

    abc
    xyz
    

    这个 一串 内容是不可变的。例如,

    // error: cannot assign to s[0]
    s[0] = '0'
    

    需要同步才能访问字符串变量。