multithreading - 在 Kotlin Native 中,如何在不使用 C 指针的情况下将对象保留在单独的线程中,并从任何其他线程中改变其状态?
问题描述
我正在探索 Kotlin Native 并有一个程序,其中有一群工人在做并发的事情(在 Windows 上运行,但这是一个普遍的问题)。
现在,我想添加简单的日志记录。一个组件,它通过将字符串作为新行附加到在“附加”模式下保持打开的文件中来简单地记录字符串。
(理想情况下,我只有一个“全局”功能......
fun log(text:String) {...} ]
...我可以从任何地方打电话,包括从其他工人的“内部”打电话,这样就可以了。这里的含义是,由于 Kotlin Native 关于在线程之间传递对象的规则(TLDR:你不应该传递可变对象。请参阅:https ://github.com/JetBrains/kotlin-native/blob /master/CONCURRENCY.md#object-transfer-and-freezing)。此外,我的日志函数理想情况下会接受任何冻结的对象。)
我想出的是使用DetachedObjectGraph的解决方案:
首先,我创建了一个分离的记录器对象
val loggerGraph = DetachedObjectGraph { FileLogger("/foo/mylogfile.txt")}
然后使用loggerGraph.asCPointer()
(asCPointer())获取COpaquePointer
分离图:
val myPointer = loggerGraph.asCPointer()
现在我可以将此指针传递给工人(通过工人执行函数的生产者 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 对象是不好的。
可以用更一般的方式提出这个问题:如何在单独的线程(或工作线程)中保持可变资源打开,并向其发送消息。
旁注:拥有真正使用线程的协程可以解决这个问题,但问题是如何使用当前可用的 API(Kotlin 1.3.61)来解决这个任务。
解决方案
您绝对不应该DetachedObjectGraph
以问题中提出的方式使用。没有什么可以阻止您尝试在多个线程上附加,或者如果您传递相同的指针,则尝试在附加的另一个线程之后附加到一个无效的线程。
正如多米尼克所说,您可以DetachedObjectGraph
将AtomicReference
. 但是,如果您要保留DetachedObjectGraph
在 中AtomicReference
,请确保类型为AtomicRef<DetachedObjectGraph?>
和忙循环,而DetachedObjectGraph
为空。这将防止DetachedObjectGraph
多个线程使用相同的内容。确保将其设置为 null,并以原子方式重新填充它。
但是,是否FileLogger
需要是可变的?如果您正在写入文件,则似乎并非如此。即使是这样,我也会将可变对象隔离到一个单独的工作人员并向其发送日志消息,而不是在DetachedObjectGraph
AtomicRef 内部进行。
以我的经验,DetachedObjectGraph
在生产代码中非常罕见。我们目前不在任何地方使用它。
要将可变状态隔离到 a 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
使用StableRef
。producer
使您想要隔离的可变状态。要记录某些内容,请调用Log.log
,这将最终调用 mutable 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}")
}
}
推荐阅读
- applescript - 检测窗口总行数
- cmake - 里面有什么
cmake 使用 macos 上的 makefile 生成器创建的 .dSYM 目录? - javascript - 在页面上有多个表单的表单中选择所有复选框
- php - 脚本执行
- python - Django ListView 不工作
- google-chrome - 我的扩展只能在 chrome webstore 中启用
- c# - 在 UWP 中将 BitmapImage 设置为 ImageSource 不起作用
- git - .gitignore 不匹配子目录
- hibernate - 会话仍然打开时没有会话错误
- fortran - Fortran 循环不工作