在 Kotlin Native 中,如何在不使用 C 指针的情况下将对象保留在单独的线程中,并从任何其他线程中改变其状态?

Posted

技术标签:

【中文标题】在 Kotlin Native 中,如何在不使用 C 指针的情况下将对象保留在单独的线程中,并从任何其他线程中改变其状态?【英文标题】:In Kotlin Native, how to keep an object around in a separate thread, and mutate its state from any other thead without using C pointers? 【发布时间】:2020-05-08 22:23:06 【问题描述】:

我正在探索 Kotlin Native 并且有一个程序有一堆 Workers 做并发的东西 (在 Windows 上运行,但这是一个普遍的问题)。

现在,我想添加简单的日志记录。一个组件,通过将字符串作为新行附加到在“附加”模式下保持打开的文件中来简单地记录字符串。

(理想情况下,我只需要一个“全局”函数...

fun log(text:String) ... ] 

...我可以从任何地方打电话,包括从其他工人的“内部”打电话,这样就可以了。这里的含义是,由于 Kotlin Native 关于在线程之间传递对象的规则(TLDR:你不应该传递可变对象。请参阅:https://github.com/JetBrains/kotlin-native/blob/master/CONCURRENCY.md#object-transfer-and-freezing),这样做并不是微不足道的。 此外,我的日志函数理想情况下会接受任何 frozen 对象。 )


我想出的是使用DetachedObjectGraph的解决方案:

首先,我创建一个分离的记录器对象

val loggerGraph = DetachedObjectGraph  FileLogger("/foo/mylogfile.txt")

然后使用loggerGraph.asCPointer() (asCPointer()) 将COpaquePointer 获取到分离图:

val myPointer = loggerGraph.asCPointer()

现在我可以将此指针传递给工人(通过工人的execute function 的producer lambda),并在那里使用它。或者我可以将指针存储在 @ThreadLocal 全局变量中。


对于写入文件的代码,每当我想记录一行时,我必须再次从指针创建一个DetachedObjectGraph 对象, 和attach() 它以获取对我的 fileLogger 对象的引用:

val fileLogger = DetachedObjectGraph(myPointer).attach()

现在我可以在记录器上调用日志函数了:

fileLogger.log("My log message")

这是我在查看可用于 Kotlin Native 中并发的 API(从 Kotlin 1.3.61 开始)时得出的结论, 但是 我想知道有什么更好的方法(使用 Kotlin,而不是诉诸 C)。显然,为每一行编写一个 DetachedObjectGraph 对象是不好的。


可以用更一般的方式提出这个问题:如何在单独的线程(或工作线程)中保持可变资源打开,并向其发送消息。

旁注:拥有真正使用线程的Coroutines 可以解决这个问题,但问题是如何使用当前可用的 API(Kotlin 1.3.61)来解决这个任务。

【问题讨论】:

【参考方案1】:

您绝对不应该以问题中提出的方式使用DetachedObjectGraph。没有什么可以阻止您尝试在多个线程上附加,或者如果您传递相同的指针,则尝试在附加的另一个线程之后附加到一个无效的线程。

正如多米尼克所说,您可以将DetachedObjectGraph 保留在AtomicReference 中。但是,如果您要将DetachedObjectGraph 保留在AtomicReference 中,请确保类型为AtomicRef<DetachedObjectGraph?> 和busy-loop,而DetachedObjectGraph 为空。这将防止相同的DetachedObjectGraph 被多个线程使用。确保将其设置为 null,然后以原子方式重新填充它。

但是,FileLogger 是否需要可变?如果您正在写入文件,则似乎并非如此。即使是这样,我也会将可变对象隔离到单独的工作人员并向其发送日志消息,而不是在 AtomicRef 内执行 DetachedObjectGraph

根据我的经验,DetachedObjectGraph 在生产代码中非常罕见。我们目前不在任何地方使用它。

要将可变状态隔离到Worker,如下所示:


class MutableThing<T:Any>(private val worker:Worker = Worker.start(), producer:()->T)
    private val arStable = AtomicReference<StableRef<T>?>(null)
    init 
        worker.execute(TransferMode.SAFE, Pair(arStable, producer).freeze())
            it.first.value = StableRef.create(it.second()).freeze()
        
    
    fun <R> access(block:(T)->R):R
        return worker.execute(TransferMode.SAFE, Pair(arStable, block).freeze())
            it.second(it.first.value!!.get())
        .result
    


object Log
    private val fileLogger = MutableThing  FileLogger() 

    fun log(s:String)
        fileLogger.access  fl -> fl.log(s) 
    


class FileLogger
    fun log(s:String)

MutableThing 在内部使用StableRefproducer 使您想要隔离的可变状态。要记录某些内容,请调用 Log.log,它最终会调用可变的 FileLogger

要查看MutableThing 的基本示例,请运行以下测试:

@Test
fun goIso()
    val mt = MutableThing  mutableListOf("a", "b")
    val workers = Array(4)Worker.start()
    val futures = mutableListOf<Future<*>>()
    repeat(1000)  rcount ->
        val future = workers[rcount % workers.size].execute(
            TransferMode.SAFE,
             Pair(mt, rcount).freeze() 
        )  pair ->
            pair.first.access 
                val element = "ttt $pair.second"
                println(element)
                it.add(element)
            
        
        futures.add(future)
    

    futures.forEach  it.result 

    workers.forEach  it.requestTermination() 

    mt.access 
        println("size: $it.size")
    

【讨论】:

实现了一个更正式的版本,以及相关的博文:dev.to/touchlab/kotlin-native-transferring-state-4n8i 酷。让我很高兴我打扰了你最终创建了这篇对每个人都有帮助的帖子!谢谢!【参考方案2】:

你所采取的方法是非常正确的,也是应该做的。

我要补充的是,而不是传递一个指针。您应该传递一个冻结的FileLogger,它将在内部保存对AtomicRef&lt;DetachedObjectGraph&gt; 的引用,附加和分离应该在内部完成。尤其是DetachedObjectGraphs 绑定后无效。

【讨论】:

以上是关于在 Kotlin Native 中,如何在不使用 C 指针的情况下将对象保留在单独的线程中,并从任何其他线程中改变其状态?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Kotlin Native 中使用 Http Request 库

如何使用 Kotlin 同时定位 JVM/Native 和 Android

Kotlin/Native 垃圾收集器如何在 C 中工作?

Kotlin Multiplatform Mobile:Ktor - 如何在 Kotlin Native(iOS)中取消活动协程(网络请求、后台工作)?

如何使用 Kotlin/native 生成​​依赖于另一个的 .framework?

使用来自 Kotlin/Native 和 Gradle 的 Apple 依赖项,更具体地说是 SwiftUI