当前位置: 首页 > 面试题库 >

追加不是线程安全的吗?

爱琪
2023-03-14
问题内容

我注意到,如果我尝试在for循环内使用goroutines将其追加到切片,那么在某些情况下我会丢失/空白数据:

destSlice := make([]myClass, 0)

var wg sync.WaitGroup
for _, myObject := range sourceSlice {
    wg.Add(1)
    go func(closureMyObject myClass) {
        defer wg.Done()
        var tmpObj myClass
        tmpObj.AttributeName = closureMyObject.AttributeName
        destSlice = append(destSlice, tmpObj)
    }(myObject)
}
wg.Wait()

有时,当我AttributeName从中打印all
destSlice,某些元素是空字符串(""),而在另一些sourceSlice情况下,in中则不存在某些元素destSlice

我的代码是否存在数据争用,是否意味着append对于多个goroutine并发使用来说不是线程安全的?


问题答案:

在Go中,没有值可以安全地进行并行读/写操作,切片(切片头)也不例外。

是的,您的代码存在数据竞争。运行带有-race选项的验证。

例:

type myClass struct {
    AttributeName string
}
sourceSlice := make([]myClass, 100)

destSlice := make([]myClass, 0)

var wg sync.WaitGroup
for _, myObject := range sourceSlice {
    wg.Add(1)
    go func(closureMyObject myClass) {
        defer wg.Done()
        var tmpObj myClass
        tmpObj.AttributeName = closureMyObject.AttributeName
        destSlice = append(destSlice, tmpObj)
    }(myObject)
}
wg.Wait()

运行它

go run -race play.go

输出为:

==================
WARNING: DATA RACE
Read at 0x00c420074000 by goroutine 6:
  main.main.func1()
      /home/icza/gows/src/play/play.go:20 +0x69

Previous write at 0x00c420074000 by goroutine 5:
  main.main.func1()
      /home/icza/gows/src/play/play.go:20 +0x106

Goroutine 6 (running) created at:
  main.main()
      /home/icza/gows/src/play/play.go:21 +0x1cb

Goroutine 5 (running) created at:
  main.main()
      /home/icza/gows/src/play/play.go:21 +0x1cb
==================
==================
WARNING: DATA RACE
Read at 0x00c42007e000 by goroutine 6:
  runtime.growslice()
      /usr/local/go/src/runtime/slice.go:82 +0x0
  main.main.func1()
      /home/icza/gows/src/play/play.go:20 +0x1a7

Previous write at 0x00c42007e000 by goroutine 5:
  main.main.func1()
      /home/icza/gows/src/play/play.go:20 +0xc4

Goroutine 6 (running) created at:
  main.main()
      /home/icza/gows/src/play/play.go:21 +0x1cb

Goroutine 5 (running) created at:
  main.main()
      /home/icza/gows/src/play/play.go:21 +0x1cb
==================
==================
WARNING: DATA RACE
Write at 0x00c420098120 by goroutine 80:
  main.main.func1()
      /home/icza/gows/src/play/play.go:20 +0xc4

Previous write at 0x00c420098120 by goroutine 70:
  main.main.func1()
      /home/icza/gows/src/play/play.go:20 +0xc4

Goroutine 80 (running) created at:
  main.main()
      /home/icza/gows/src/play/play.go:21 +0x1cb

Goroutine 70 (running) created at:
  main.main()
      /home/icza/gows/src/play/play.go:21 +0x1cb
==================
Found 3 data race(s)
exit status 66

解决方法很简单,使用一个sync.Mutex保护写destSlice值:

var (
    mu        = &sync.Mutex{}
    destSlice = make([]myClass, 0)
)

var wg sync.WaitGroup
for _, myObject := range sourceSlice {
    wg.Add(1)
    go func(closureMyObject myClass) {
        defer wg.Done()
        var tmpObj myClass
        tmpObj.AttributeName = closureMyObject.AttributeName
        mu.Lock()
        destSlice = append(destSlice, tmpObj)
        mu.Unlock()
    }(myObject)
}
wg.Wait()

您还可以通过其他方式解决它,例如,可以使用一个通道,在该通道上发送要附加的值,并从该通道接收指定的goroutine并执行附加操作。



 类似资料:
  • 在我的应用程序中,我使用多个线程来处理客户端连接。 我在调试时发现了一个非常奇怪的行为——我有一个SelectionKey,通过调用(使用调试器)它的interestTops()方法,返回值是1(READ),但当我将数据发送到与该键对应的套接字时,选择器不会被唤醒。。 如果使用调试器,我将特定选择键更改为1(即使是1),选择器会突然对该更改做出反应。 在给定的时间内,我只有一个线程处理一个连接,但

  • 问题内容: 在中,此变量被声明为是我的问题,在某些调用之后检查值还是在多线程代码中使用perror()是安全的。这是线程安全变量吗?如果没有,那还有什么选择呢? 我在x86体系结构上将Linux与gcc一起使用。 问题答案: 是的,它是线程安全的。在Linux上,全局errno变量是特定于线程的。POSIX要求errno必须是线程安全的。 参见http://www.unix.org/whitepa

  • 问题内容: 我们在项目中使用了Drools kieSessions。许多线程可以创建新的kieSession。有时在创建会话时线程可能会挂起。因此,问题是: 首先 是kieContainer.newKieSession线程安全操作吗? 上吊的原因可能是肮脏的文字或阅读的kie会话集之类的东西吗? 问题答案: 当我在实践中检查 不是线程安全的操作。

  • 问题内容: 在Java中:线程安全吗,即返回的迭代器是在任何时候反映列表的当前状态,还是仅在创建列表时反映列表的状态? 问题答案: List.iterator()的行为未定义或与其他List实现保持一致。 对于ArrayList,LinkedList,如果在迭代列表时对其进行了修改,则可以获得ConcurrentModificationException。(这不能保证)避免此问题的方法是使用syn

  • 我看到了不同的PHP二进制文件,比如非线程或线程安全? 这是什么意思? 这些软件包之间有什么区别?

  • 问题内容: 阅读“实践中的Java并发性”,第3.5节包含以下内容: 除了创建两个的明显的线程安全隐患外,该书还声称可能会发生发布问题。 此外,对于诸如 一个可以扔! 这怎么可能?我能想到的唯一允许这种荒谬行为的方法是,如果构造函数不被阻塞,那么当构造函数代码仍在另一个线程中运行时,将创建对实例的引用。 这可能吗? 问题答案: 之所以可行,是因为Java的内存模型较弱。它不保证读写顺序。 可以通过