我知道我在mutable.ListBuffer
做错了,但我无法弄清楚如何修复它(以及对问题的正确解释)。
我简化了下面的代码以重现行为。
我基本上尝试并行运行函数,以便在我的第一个列表被处理时将元素添加到列表中。我最终“失去”元素。
import java.util.Properties
import scala.collection.mutable.ListBuffer
import scala.concurrent.duration.Duration
import scala.concurrent.{Await, Future}
import scala.concurrent.{ExecutionContext}
import ExecutionContext.Implicits.global
object MyTestObject {
var listBufferOfInts = new ListBuffer[Int]() // files that are processed
def runFunction(): Int = {
listBufferOfInts = new ListBuffer[Int]()
val inputListOfInts = 1 to 1000
val fut = Future.traverse(inputListOfInts) { i =>
Future {
appendElem(i)
}
}
Await.ready(fut, Duration.Inf)
listBufferOfInts.length
}
def appendElem(elem: Int): Unit = {
listBufferOfInts ++= List(elem)
}
}
MyTestObject.runFunction()
MyTestObject.runFunction()
MyTestObject.runFunction()
返回:
res0: Int = 937
res1: Int = 992
res2: Int = 997
显然我希望1000
能够一直返回。如何修复我的代码以保持“架构”但让我的ListBuffer“同步”?
答案 0 :(得分:3)
我不知道究竟是什么问题,因为你说你简化了它,但你仍然有一个明显的竞争条件,多个线程修改一个可变集合,这是非常糟糕的。正如其他答案所指出的,你需要一些锁定,这样只有一个线程可以同时修改集合。如果你的计算量很大,那么将结果以同步的方式附加到缓冲区不应该显着影响性能,但是如果有疑问则总是测量。
但是不需要同步,你可以做其他事情,没有变量和可变状态。让每个Future
返回您的部分结果,然后将它们合并到一个列表中,事实上Future.traverse
就是这样。
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
import scala.concurrent.ExecutionContext.Implicits.global
def runFunction: Int = {
val inputListOfInts = 1 to 1000
val fut: Future[List[Int]] = Future.traverse(inputListOfInts.toList) { i =>
Future {
// some heavy calculations on i
i * 4
}
}
val listOfInts = Await.result(fut, Duration.Inf)
listOfInts.size
}
Future.traverse
已经为您提供了一个包含所有结果的不可变列表,无需将它们附加到可变缓冲区。
不用说,你总会得到1000
。
@ List.fill(10000)(runFunction).exists(_ != 1000)
res18: Boolean = false
答案 1 :(得分:1)
我不确定上面是否显示了您正在尝试正确执行的操作。也许问题是你实际上正在共享一个var ListBuffer,你可以在runFunction中重新初始化。
当我把它拿出来时,我收集了我正确期待的所有事件:
import java.util.Properties
import scala.collection.mutable.ListBuffer
import scala.concurrent.duration.Duration
import scala.concurrent.{ Await, Future }
import scala.concurrent.{ ExecutionContext }
import ExecutionContext.Implicits.global
object BrokenTestObject extends App {
var listBufferOfInts = ( new ListBuffer[Int]() )
def runFunction(): Int = {
val inputListOfInts = 1 to 1000
val fut = Future.traverse(inputListOfInts) { i =>
Future {
appendElem(i)
}
}
Await.ready(fut, Duration.Inf)
listBufferOfInts.length
}
def appendElem(elem: Int): Unit = {
listBufferOfInts.append( elem )
}
BrokenTestObject.runFunction()
BrokenTestObject.runFunction()
BrokenTestObject.runFunction()
println(s"collected ${listBufferOfInts.length} elements")
}
如果您确实遇到同步问题,可以使用以下内容:
import java.util.Properties
import scala.collection.mutable.ListBuffer
import scala.concurrent.duration.Duration
import scala.concurrent.{ Await, Future }
import scala.concurrent.{ ExecutionContext }
import ExecutionContext.Implicits.global
class WrappedListBuffer(val lb: ListBuffer[Int]) {
def append(i: Int) {
this.synchronized {
lb.append(i)
}
}
}
object MyTestObject extends App {
var listBufferOfInts = new WrappedListBuffer( new ListBuffer[Int]() )
def runFunction(): Int = {
val inputListOfInts = 1 to 1000
val fut = Future.traverse(inputListOfInts) { i =>
Future {
appendElem(i)
}
}
Await.ready(fut, Duration.Inf)
listBufferOfInts.lb.length
}
def appendElem(elem: Int): Unit = {
listBufferOfInts.append( elem )
}
MyTestObject.runFunction()
MyTestObject.runFunction()
MyTestObject.runFunction()
println(s"collected ${listBufferOfInts.lb.size} elements")
}
答案 2 :(得分:0)
更改
listBufferOfInts ++= List(elem)
到
synchronized {
listBufferOfInts ++= List(elem)
}
让它发挥作用。可能会成为性能问题?我仍然对解释感兴趣,也许是一种更好的做事方式!