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

如果数组是值类型并因此被复制,那么它们如何不是线程安全的?

商棋
2023-03-14
问题内容

值类型的实例不共享:每个线程都有自己的副本。*这意味着每个线程都可以读写其实例,而不必担心其他线程在做什么。

然后我被带到了这个答案及其评论

并被告知:

从多个线程访问的数组本身本身不是线程安全的,因此所有交互必须同步。

&告诉我 每个线程都有自己的副本

如果一个线程正在更新数组(大概是这样,您就可以从另一个队列中看到该编辑内容),那根本就不适用

根本不适用 <-为什么不呢?

我最初以为所有这些事情都是在发生,因为数组(即值类型)被包装到一个类中,但是令我惊讶的是,我被告知不是真的!所以我又回到了Swift 101:D


问题答案:

根本问题是“每个线程都有自己的副本”的解释。

是的,我们经常使用值类型,通过为每个线程提供自己的对象副本(例如数组)来确保线程安全。但这与声称值类型保证每个线程将获得自己的副本不同。

具体来说,使用闭包,多个线程可以尝试变异同一值类型的对象。这是一个代码html" target="_blank">示例,显示了一些与Swift Array值类型交互的非线程安全代码:

let queue = DispatchQueue.global()

var employees = ["Bill", "Bob", "Joe"]

queue.async {
    let count = employees.count
    for index in 0 ..< count {
        print("\(employees[index])")
        Thread.sleep(forTimeInterval: 1)
    }
}

queue.async { 
    Thread.sleep(forTimeInterval: 0.5)
    employees.remove(at: 0)
}

(您通常不会添加sleep调用;我只是将它们添加到清单竞争条件中,否则这些条件很难重现。您也不应在没有同步的情况下从多个线程中更改对象,但是我这样做是为了说明问题。)

在这些async调用中,您仍在引用employees先前定义的同一数组。因此,在此特定示例中,我们将看到它输出“ Bill”,它将跳过“
Bob”(即使已删除“ Bill”),它将输出“ Joe”(现在是第二项),并且那么尝试访问数组中的第三个项目将崩溃,该数组现在只剩下两个项目。

现在,我在上面说明的所有事情是,一个值类型 可以
在一个线程中被另一个线程使用,而又被另一个线程使用,从而违反了线程安全性。实际上,在编写不是线程安全的代码时,可能会出现一系列更基本的问题,但是以上只是一个稍微作弊的示例。

但是,您可以employees通过在第一个async调用中添加一个“捕获列表”
来表示要使用原始employees数组的副本,从而确保该单独的线程获得其数组的副本:

queue.async { [employees] in
    ...
}

或者,如果将此值类型作为参数传递给另一个方法,则将自动获得此行为:

doSomethingAsynchronous(with: employees) { result in
    ...
}

在这两种情况中的任何一种情况下,您都将享受值语义,并看到原始数组的副本(或写时复制),尽管原始数组可能已在其他地方进行了突变。

最重要的是,我的观点仅仅是值类型不能 保证
每个线程都有自己的副本。该Array类型不是线程安全的(也不是许多其他可变值类型)。但是,像所有值类型一样,Swift提供了简单的机制(其中一些是完全自动和透明的),这些机制将为每个线程提供自己的副本,从而使编写线程安全代码变得更加容易。

这是另一个示例,其中另一个值类型使问题更加明显。这是一个示例,其中编写线程安全代码失败返回语义上无效的对象:

let queue = DispatchQueue.global()

struct Person {
    var firstName: String
    var lastName: String
}

var person = Person(firstName: "Rob", lastName: "Ryan")

queue.async {
    Thread.sleep(forTimeInterval: 0.5)
    print("1: \(person)")
}

queue.async { 
    person.firstName = "Rachel"
    Thread.sleep(forTimeInterval: 1)
    person.lastName = "Moore"
    print("2: \(person)")
}

在此示例中,第一个打印语句将有效地说“ Rachel Ryan”,它既不是“ Rob Ryan”也不是“ Rachel
Moore”。简而言之,我们正在检查Person内部状态不一致的情况。

但是,同样,我们可以使用捕获列表来享受价值语义:

queue.async { [person] in
    Thread.sleep(forTimeInterval: 0.5)
    print("1: \(person)")
}

在这种情况下,它会说“ Rob
Ryan”,而忽略了原件Person可能正在被另一个线程变异的事实。(很明显,真正的问题并不仅是通过在第一次async调用中使用值语义来解决的,而且还同步第二次async调用和/或在那里使用值语义来解决。)



 类似资料:
  • 如果我得到一个JWT并且我可以解码有效负载,那怎么会安全呢?难道我不能直接从报头中抓取令牌,解码并更改有效负载中的用户信息,然后用同样正确的编码秘密将其发回吗? 我知道他们必须是安全的,但我只是真的想了解技术。我错过了什么?

  • 问题内容: 为了掌握Redis的一些基础知识,我遇到了一篇有趣的博客文章。 作者指出: Redis是具有epoll / kqueue的单线程,并且在I / O并发方面可以无限​​扩展。 我肯定会误解整个线程问题,因为我发现此语句令人困惑。如果程序是单线程的,它如何并发执行任何操作?如果服务器仍然是单线程的,为什么Redis操作是原子的那么好呢? 有人可以阐明这个问题吗? 问题答案: 好吧,这取决于

  • 我试图编写java代码,从用户输入中获取十个整数。然后我必须找到重复的数字。 “为什么我仍然会出错?

  • 问题内容: 我看过OpenJDK的OpenJDK源代码,似乎所有写操作都受同一锁保护,而读操作则根本不受保护。据我了解,在JMM下,对变量的所有访问(读和写)都应受锁保护,否则可能会发生重新排序的效果。 例如,method包含以下几行(处于锁定状态): 另一方面,该方法仅起作用。 在我对JMM的理解中,这意味着如果将语句1-4重新排序为1-2(new)-4-2(copyOf)-3 ,则可能会在不一

  • 所以我有一个问题,当我计算一个数字时,比如说15,我必须显示这个:15=3x5,但我得到的是3x5x5,我不知道如何使它变成这样,所以它只显示3x5。还有一个问题是,我输入的数字是否是素数。有办法解决这个问题吗?我只需要这些,然后再编辑其他东西。

  • 做对了"。事实证明,可以用来表示任何类似ListT-dod-right的东西。引用加布里埃尔·冈萨雷斯的话: 请注意,您可以仅使用变压器依赖项构建任何(不仅仅是管道中的那个)。例如,下面是如何实现: 这将在那里键入check作为any,并为所有人做正确的事情。 所以我的问题是:对于管道的消费者部分,是否有一个对偶到和到? 要求: 从不使用,只返回(或从不返回),但使用的管道可以表示为“双重到列表”