【问题标题】:Immutability of string and concurrency字符串的不变性和并发性
【发布时间】:2018-12-17 09:29:50
【问题描述】:

我们应该在编写字符串时同步吗?由于字符串是不可变的,我们永远不会在 2 个不同线程的写入和读取之间出现不一致的状态,对吧?

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

【问题讨论】:

  • 字符串是不可变的,但指针不是。如果您不使用指向字符串的指针,那么无论如何您都不会在线程之间共享数据,因此不变性是无关紧要的;如果您使用的是指针,那么您需要在它们上进行同步。

标签: string go concurrency immutability atomic


【解决方案1】:

string 值是不可变的,但变量不是。变量是——正如他们的名字所说的——变量,它们的值可以改变。

访问string 值不需要同步,这是无法更改的。如果将string 值传递给您,那么(string 的内容)将始终保持不变(不计算包unsafe 的使用)。

当你想同时从多个 goroutine 访问 string 类型的变量时,如果至少有一个访问是写入(更改 string 变量的值的写入),则需要同步。这对于 Go 中任何类型的变量都是如此,string 类型在任何方面都不是特殊的。

这在实践中意味着什么?

如果您有一个接收string"hello" 的函数,则可以确定string 值无论如何都会保持"hello"。因此,如果您不自己更改参数(例如,您不为其分配新值),它将始终保持 string"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”来修改它,并等待这个修改完成),并再次打印它,它已经改变了,但 sliceTest() 本身并没有修改它。

现在,如果 sliceTest() 将收到 string 参数,则不会发生这种情况。

查看相关/可能的重复:Immutable string and pointer address

【讨论】:

    【解决方案2】:

    在语言语法级别或标准库中定义在字符串类型上的所有函数都返回一个新的字符串实例。没有一个函数可以就地改变字符串。只要遵循这种做法,你就会同时安全。

    【讨论】:

      【解决方案3】:

      我们应该在编写字符串时同步吗?由于字符串是不可变的,我们 永远不会在从 2 的写入和读取之间获得不一致的状态 不同的线程,对吧?

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


      Go 编译器将强制 string 内容的不变性。例如,

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

      输出:

      5:7: cannot assign to s[0]
      

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

      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

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


      string 变量不是不可变的。它包含一个字符串描述符,一个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
      

      string 内容是不可变的。例如,

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-04-15
        • 2012-05-04
        • 2011-04-05
        • 2010-12-05
        • 1970-01-01
        • 2010-10-03
        • 2016-07-04
        相关资源
        最近更新 更多