Sendable协议如何帮助防止数据竞争

Sendable协议如何帮助防止数据竞争

在我之前的文章中,您了解到,actor可以通过确保其可变状态互斥来帮助我们防止数据竞争。这种说法是正确的,条件是我们在actor内部访问其可变状态。如果可变状态可以在actors之外访问,数据竞争仍然可能发生!

在本文中,让我们探索这种数据竞争如何发生,以及Sendable协议如何帮助防止这种情况发生。除此之外,我们还将看看苹果未来将为Sendable带来的改进,以应对这种情况。

所以,不用多说,让我们开始吧!

从actor外传递数据时的数据竞争

假设我们有一个Article文章类,它有一个likeCount变量,可以跟踪文章从读者那里获得的点赞数量。

final class Article {

    let title: String
    var likeCount = 0

    init(title: String) {
        self.title = title
    }
}

我们还有一个ArticleManager actor ,负责管理一系列文章:

actor ArticleManager {

    private let articles = [
        Article(title:Swift Senpai Article 01),
        Article(title:Swift Senpai Article 02),
        Article(title:Swift Senpai Article 03),
    ]

    /// Increase like count by 1
    func like(_ articleTitle: String) {

        guard let article = getArticle(with: articleTitle) else {
            return
        }

        article.likeCount += 1
    }

    /// Get article based on article title
    func getArticle(with articleTitle: String) -> Article? {
        return articles.filter({ $0.title == articleTitle }).first
    }
}

请注意,ArticleManager演员有一个like(_:)函数,可以增加特定文章的likeCount,以及一个getArticle(with:)函数,可以根据给定的文章标题返回文章。

由于存在getArticle(with:)功能,ArticleManager的文章现在可以在actor之外访问。换句话说,actor的可变状态现在可以在actor之外更新,从而存在潜在数据竞争。

现在,考虑以下位于actor之外dislike(_:)函数:

let manager = ArticleManager()

/// Access article outside of the actor and reduces its like count by 1
func dislike(_ articleTitle: String) async {

    guard let article = await manager.getArticle(with: articleTitle) else {
        return
    }

    // Reduce like count
    article.likeCount -= 1
}

由于我们现在在actor之外减少(修改)文章的点赞数,如果我们尝试同时运行actor的like(_:)和上述 dislike(_:)函数,将发生数据竞争!

let articleTitle =Swift Senpai Article 01// Create a parent task
Task {

    // Create a task group
    await withTaskGroup(of: Void.self, body: { taskGroup in

        // Create 3000 child tasks to like
        for _ in 0..<3000 {
            taskGroup.addTask {
                await self.manager.like(articleTitle)
            }
        }

        // Create 1000 child tasks to dislike
        for _ in 0..<1000 {
            taskGroup.addTask {
                await self.dislike(articleTitle)
            }
        }
    })

    print(“👍🏻 Like count: \(await manager.getArticle(with: articleTitle)!.likeCount))
}

在上述代码中,我们创建了3000个子任务来点赞一篇文章,1000个子任务来同时点赞和踩同一篇文章。最后,即使我们能够获得“👍🏻like count:2000” 的输出,Xcode的 thread sanitizer仍然会显示线程问题,这表明数据竞争确实发生了。

专业提示:

您可以通过导航到Product>Schema>Edit Scema来启用Thread Sanitizer…之后,在Edit Schema对话框中选择Run>Diagnostic> 勾选Thread Sanitizer复选框。

既然你已经看到了在actor之外修改actor状态如何导致数据竞争,我们可以做些什么来防止这种情况发生?

通过添加一致性来检查可发送

Sendable是Swift 5.5中引入的一种新协议,与async/await和actors一起引入。Sendable类型的值可以在不同的actors之间共享。如果我们将一个值从一个地方复制到另一个地方,并且两个地方都可以安全地修改该值的副本,而不会相互干扰,那么该类型将被视为Sendable类型。要了解更多信息,您可以看看这个WWDC视频

回到我们的示例代码,为了避免在ArticleManager之外修改文章时出现数据竞争,我们必须使Article类型符合Sendable协议。我们继续做吧。

final class Article: Sendable {

    let title: String
    var likeCount = 0

    init(title: String) {
        self.title = title
    }
}

在这个阶段,编译器将开始抱怨:
** “Stored property ‘likeCount’ of ‘Sendable’-conforming class ‘Article’ is mutable“**.

在这里插入图片描述

此错误意味着,如果我们希望Article类型是可发送的,我们就不能拥有可变的存储属性(likeCount)。 这是因为同时与可变存储属性共享引用类型不安全,会引起潜在数据竞争。

就我们而言,我们绝对不能让likeCount成为常数,那么我们还有什么其他选择呢? 根据苹果的说法,有许多不同类型的类型是可以发送的:

在这里插入图片描述

我们有一个选择是将Article更改为值类型。

struct Article: Sendable {

    let title: String
    var likeCount = 0

    init(title: String) {
        self.title = title
    }
}

这一次,Article结构没有给我们任何编译器错误,但我们在示例代码的其他一些地方确实收到了多个编译器错误。
在这里插入图片描述

修复所有这些编译器错误确实需要相当多的代码更改,但这绝对是可行的。 因此,我会把这个作为练习留给你。 如果您确实陷入困境,可随时在这里找到解决方案。

正如您从上面的示例中看到的,遵守Sendable可发送协议不会自动使一种数据类型可发送。 然而,它确实执行了我们需要遵循的规则,以免意外地为数据竞争创造潜力。 因此,下次您处理Actor时,请确保使任何可共享的可变状态符合Sendable协议。

期待未来的改善

在WWDC中,苹果提到,未来,Swift编译器将阻止我们共享不可发送的类型。 如果我们尝试访问actor的不可发送状态,我们将收到编译器错误,如下所示:

在这里插入图片描述

至于Swift 5.5,此检查仍然不可用,苹果还没有关于何时可用的信息。 当actor内部有可共享的类型时,目前我们能做的最好的事情是注意并始终遵守Sendable协议,

小结

在我们的ArticleManager示例中,防止数据竞争的最佳方法肯定是将整个dislike(_:)函数移动到ArticleManager中。出于演示目的,我特意使用Sendable方法,以便您可以更好地了解Sendable协议如何帮助防止actor 之外的数据竞争。

在撰写本文时,Swift工程团队仍在积极改进Sendable检查。如果您有任何意见或想法想分享,请务必在Swift论坛上与他们联系。

最后但并非最不重要的是,您可以在这里获得完整的示例代码

有关iOS开发和Swift的更多文章,请务必在Twitter上关注我并订阅我的每月时事通讯。

感谢您的阅读。👨🏻‍💻

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值