Suppose you are given the following code:
class FooBar { public void foo() { for (int i = 0; i < n; i++) { print("foo"); } } public void bar() { for (int i = 0; i < n; i++) { print("bar"); } } }
The same instance of FooBar
will be passed to two different threads:
A
will call foo()
, whileB
will call bar()
.Modify the given program to output "foobar"
n
times.
Example 1:
Input: n = 1 Output: "foobar" Explanation: There are two threads being fired asynchronously. One of them calls foo(), while the other calls bar(). "foobar" is being output 1 time.
Example 2:
Input: n = 2 Output: "foobarfoobar" Explanation: "foobar" is being output 2 times.
Constraints:
1 <= n <= 1000
This problem requires printing "foo" and "bar" alternately n
times using two threads. The solution employs semaphores for thread synchronization. Semaphores are signaling mechanisms that allow threads to coordinate their actions.
The core idea is to use two semaphores:
f
: Controls access to the foo
function. Initialized to 1 (allows foo
to run initially).b
: Controls access to the bar
function. Initialized to 0 (prevents bar
from running initially).The threads operate as follows:
Thread A (foo):
f
semaphore (waits if f
is 0). This ensures that foo
runs only when allowed.b
semaphore. This signals that bar
can now run.Thread B (bar):
b
semaphore (waits if b
is 0). This ensures that bar
runs only when allowed.f
semaphore. This signals that foo
can now run again.This alternating acquisition and release of semaphores guarantees the "foobar" sequence. The loop ensures that this sequence repeats n
times.
n
times. The semaphore operations (acquire and release) are generally considered constant time operations.n
. The semaphores are fixed-size.The code implementations below demonstrate this approach. Each language has its own semaphore mechanism:
from threading import Semaphore
class FooBar:
def __init__(self, n):
self.n = n
self.f = Semaphore(1) # Semaphore for foo, initialized to 1
self.b = Semaphore(0) # Semaphore for bar, initialized to 0
def foo(self, printFoo):
for _ in range(self.n):
self.f.acquire() # Acquire the foo semaphore
printFoo() # Print "foo"
self.b.release() # Release the bar semaphore
def bar(self, printBar):
for _ in range(self.n):
self.b.acquire() # Acquire the bar semaphore
printBar() # Print "bar"
self.f.release() # Release the foo semaphore
import java.util.concurrent.Semaphore;
class FooBar {
private int n;
private Semaphore f = new Semaphore(1); // Semaphore for foo, initialized to 1
private Semaphore b = new Semaphore(0); // Semaphore for bar, initialized to 0
public FooBar(int n) {
this.n = n;
}
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
f.acquire(); // Acquire the foo semaphore
printFoo.run(); // Print "foo"
b.release(); // Release the bar semaphore
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
b.acquire(); // Acquire the bar semaphore
printBar.run(); // Print "bar"
f.release(); // Release the foo semaphore
}
}
}
#include <semaphore>
#include <functional>
class FooBar {
private:
int n;
std::binary_semaphore f{1}; // Semaphore for foo, initialized to 1
std::binary_semaphore b{0}; // Semaphore for bar, initialized to 0
public:
FooBar(int n) : n(n) {}
void foo(std::function<void()> printFoo) {
for (int i = 0; i < n; ++i) {
f.acquire(); // Acquire the foo semaphore
printFoo(); // Print "foo"
b.release(); // Release the bar semaphore
}
}
void bar(std::function<void()> printBar) {
for (int i = 0; i < n; ++i) {
b.acquire(); // Acquire the bar semaphore
printBar(); // Print "bar"
f.release(); // Release the foo semaphore
}
}
};
These code snippets show the essential logic for solving the problem using semaphores. Remember to handle potential exceptions (like InterruptedException
in Java) in a production environment. The printFoo
and printBar
functions are placeholders for the actual printing logic provided by the LeetCode problem.