Scala печатает foo / bar поочередно

#scala #concurrency

#scala #параллелизм

Вопрос:

Я пытаюсь закодировать это упражнение с LeetCode для печати foo / bar поочередно в Scala, используя обычные Runnables с wait(), notifyAll(), но не могу заставить его выдавать желаемый результат, который должен быть:

 foo bar foo bar foo bar foo bar foo bar 
 

Вот код:

 import scala.concurrent.ExecutionContext.Implicits.global

class Foo extends Runnable {
  @Override def run(): Unit = { print("foo ") }
}

class Bar extends Runnable {
  @Override def run(): Unit = { print("bar ") }
}

val printFoo = new Foo
val printBar = new Bar

class FooBar {
  private var foosLoop: Boolean = false

  @throws(classOf[InterruptedException])
  def foo: Unit = for (_ <- 1 to 5) synchronized {
      while (foosLoop) { wait() }
      printFoo.run()
      foosLoop = true
      notifyAll()
    }

  @throws(classOf[InterruptedException])
  def bar: Unit = for (_ <- 1 to 5) synchronized {
      while (!foosLoop) { wait() }
      printBar.run()
      foosLoop = false
      notifyAll()
    }
}

val fb = new FooBar
fb.foo
fb.bar

// Output:
// foo    <=== prints only first "foo "
 

Может кто-нибудь помочь мне понять, что я сделал не так?

Мой второй вопрос: может ли это быть реализовано с помощью Scala Futures, заменяющих Runnables?

Обновить:

Опубликованный код фактически работает до тех пор, пока fb.foo и fb.bar должен вызываться из отдельных потоков.

 val tFoo = new Thread (new Runnable { @Override def run(): Unit = fb.foo })
val tBar = new Thread (new Runnable { @Override def run(): Unit = fb.bar })

tFoo.start()
tBar.start()
 

Комментарии:

1. Что вы уже пробовали сами?

Ответ №1:

Может кто-нибудь помочь мне понять, что я сделал не так?

Понятия не имею, я не использовал Runnables в своей жизни, и они не используются в Scala.
(и я бы сказал, что они также больше не используются в Java)

Может ли это быть реализовано с помощью Scala Futures, заменяющих Runnables?

Да, что-то вроде этого:

 import java.util.concurrent.Semaphore
import scala.concurrent.{ExecutionContext, Future}

object RunAlternately {
  /**
   * Runs two taks concurrently and alternating between the two.
   * @param n the amout of times to run each task.
   * @param aTaks the first task.
   * @param bTaks the second task.
   */
  def apply(n: Int)(aTask: => Unit)(bTask: => Unit)(implicit ec: ExecutionContext): Future[Unit] ={
    val aLock = new Semaphore(1)
    val bLock = new Semaphore(0)
    
    def runOne(task: => Unit, thisLock: Semaphore, thatLock: Semaphore): Future[Unit] =
      Future {
        var i = 0
        while (i < n) {
          thisLock.acquire()
          task
          thatLock.release()
          i  = 1
        }
      }
    
    val aFuture = runOne(aTask, thisLock = aLock, thatLock = bLock)
    val bFuture = runOne(bTask, thisLock = bLock, thatLock = aLock)
    
    aFuture.flatMap(_ => bFuture)
  }
}
 

Посмотрите, как он работает здесь.


Однако такого рода вещи обычно лучше моделируются с помощью API еще более высокого уровня, таких как IO или Streams.