# 1114. Print in Order

## Description

Suppose we have a class:

public class Foo {
public void first() { print("first"); }
public void second() { print("second"); }
public void third() { print("third"); }
}


The same instance of Foo will be passed to three different threads. Thread A will call first(), thread B will call second(), and thread C will call third(). Design a mechanism and modify the program to ensure that second() is executed after first(), and third() is executed after second().

Note:

We do not know how the threads will be scheduled in the operating system, even though the numbers in the input seem to imply the ordering. The input format you see is mainly to ensure our tests' comprehensiveness.

Example 1:

Input: nums = [1,2,3]
Output: "firstsecondthird"
Explanation: There are three threads being fired asynchronously. The input [1,2,3] means thread A calls first(), thread B calls second(), and thread C calls third(). "firstsecondthird" is the correct output.


Example 2:

Input: nums = [1,3,2]
Output: "firstsecondthird"
Explanation: The input [1,3,2] means thread A calls first(), thread B calls third(), and thread C calls second(). "firstsecondthird" is the correct output.


Constraints:

• nums is a permutation of [1, 2, 3].

## Solutions

Solution 1: Multithreading + Lock or Semaphore

We can use three semaphores $a$, $b$, and $c$ to control the execution order of the three threads. Initially, the count of semaphore $a$ is $1$, and the counts of $b$ and $c$ are $0$.

When thread $A$ executes the first() method, it first needs to acquire semaphore $a$. After acquiring successfully, it executes the first() method, and then releases semaphore $b$. This allows thread $B$ to acquire semaphore $b$ and execute the second() method.

When thread $B$ executes the second() method, it first needs to acquire semaphore $b$. After acquiring successfully, it executes the second() method, and then releases semaphore $c$. This allows thread $C$ to acquire semaphore $c$ and execute the third() method.

When thread $C$ executes the third() method, it first needs to acquire semaphore $c$. After acquiring successfully, it executes the third() method, and then releases semaphore $a$. This allows thread $A$ to acquire semaphore $a$ and execute the first() method.

The time complexity is $O(1)$, and the space complexity is $O(1)$.

In Python, a Barrier is a synchronization primitive that is used in concurrent programming. It allows multiple threads to wait for each other at a certain point in the code, ensuring that all the participating threads have reached this point before any of them can proceed. This is useful in scenarios where a part of the computation in one thread depends on the completion of similar parts in other threads.

Here’s an example using the Barrier from Python’s threading module:

import threading
import time
import random

print(f'{name} is waiting for the barrier.')
time.sleep(random.randint(1, 5))  # Simulate work
barrier.wait()  # Wait for all threads to arrive at this point
print(f'{name} has crossed the barrier.')

# Create a barrier for 3 threads

# Start 3 threads that will wait on the barrier

t.start()

t.join()

print('All threads have crossed the barrier.')


In this example, three threads are started, and each of them executes the task function. Inside task, each thread first prints a message indicating it’s waiting for the barrier. Then, it simulates some work by sleeping for a random amount of time between 1 and 5 seconds. After the simulated work, the thread calls barrier.wait(), which causes the thread to wait until all three threads have reached this point.

Once all threads have called barrier.wait(), they are all released to proceed past the barrier point. This is indicated by printing that each thread has “crossed the barrier”. Finally, the main thread waits for all threads to complete by joining them and then prints that all threads have crossed the barrier.

This Barrier example demonstrates how to synchronize threads in Python, ensuring that certain parts of your program in different threads run in step with each other.

The Event object from Python’s threading module is a simple synchronization primitive that is used to communicate between threads. One thread signals an event, and other threads can wait for that event to be set. Here’s an example to demonstrate how Event can be used in Python:

from threading import Event, Thread
import time

def waiter(event, name):
print(f'{name} is waiting for the event to be set.')
event.wait()  # Block until the event is set.
print(f'{name} has detected the event is set and is proceeding.')

def setter(event, delay):
print('Event setter is sleeping.')
time.sleep(delay)  # Simulate doing some work for a while
event.set()  # Set the event, all waiting threads are unblocked.
print('Event setter has set the event. Waiting threads are released.')

# Create an Event object
event = Event()

# Start a thread that will wait for the event to be set

# Start a thread that will set the event after a delay



In this example, two “waiter” threads are waiting for an event to be set, and a “setter” thread sets the event after a delay of 3 seconds. The event.wait() call in the waiter threads blocks those threads until the event is set by calling event.set() in the setter thread. Once the event is set, all waiting threads are unblocked and proceed with their execution.

This mechanism allows for simple communication between threads, where an event can signal that some condition has been met, a task has been completed, or a certain point in the program flow has been reached.

In Python, a Condition object from the threading module is a synchronization primitive that allows one or more threads to wait until they are notified by another thread. This can be useful in situations where some threads need to wait for some condition to become true before proceeding. The Condition provides methods for acquiring and releasing a lock, waiting for a condition to become true, and notifying other threads that the condition may be true now.

Here’s an example to demonstrate how to use a Condition:

import threading
import time

# A list to use as the shared resource or condition
items = []

def __init__(self, name):
self.name = name

def run(self):
global items
while True:
with condition:
if not items:
print(f'{self.name} is waiting for items to be added')
condition.wait()  # Wait until an item is available
item = items.pop()
print(f'{self.name} consumed an item: {item}')

def __init__(self, name):
self.name = name

def run(self):
global items
for i in range(5):
time.sleep(2)  # Simulate time to produce an item
with condition:
item = f'item {i}'
items.append(item)
print(f'{self.name} produced an item: {item}')
condition.notify()  # Notify waiting consumers an item is available

# Create and start producer and consumer threads
producer = Producer('Producer')
consumer = Consumer('Consumer')

producer.start()
consumer.start()

producer.join()
consumer.join()


In this example:

• The Producer thread produces items (in this case, just strings) and adds them to the items list. After producing an item, it calls condition.notify() to signal to the Consumer threads that a new item is available.
• The Consumer thread waits for items to be added to the list by calling condition.wait(). This call will block the Consumer until the Producer adds an item to the list and calls condition.notify().
• Both Producer and Consumer use with condition: to automatically acquire a lock before modifying the items list and to release the lock afterward. This ensures that the list is not accessed by multiple threads at the same time, preventing race conditions.

This example demonstrates how Condition objects can be used to synchronize threads in a producer-consumer scenario.

threading.Lock is a synchronization primitive that is used to enforce limits on access to a resource in a multi-threaded environment. It is one of the simplest locking mechanisms provided by the threading module in Python. A lock can be in one of two states: “locked” or “unlocked”. It has two basic methods: .acquire() (to lock) and .release() (to unlock).

Here is a basic example of using a threading.Lock:

import threading

# Shared resource
counter = 0

# Create a lock

# A function to increment global counter
def increment():
global counter
for _ in range(100000):
# Acquire the lock
lock.acquire()
counter += 1
# Release the lock
lock.release()

# Wait for both threads to finish

print(f"The final counter value is {counter}")


In this example, two threads are incrementing a shared global counter. The lock.acquire() method is used to ensure that only one thread can enter the critical section of code at any time to increment the counter. This prevents race conditions and ensures that the final value of the counter is correct.

Difference Between Lock and Semaphore:

• Lock: A lock allows only one thread to access the critical section of code at a time. When a thread acquires a lock, any other thread that attempts to acquire it will be blocked until the lock is released by the current owner. Locks are typically binary (either locked or unlocked).

• Semaphore: A semaphore allows a fixed number of threads to access the critical section. It’s initialized with a given count, which represents the number of threads allowed to access the critical section concurrently. When a thread enters the section, it decrements the count, and when it exits, it increments the count. If the count reaches zero, subsequent threads attempting to enter will block until the count is greater than zero again.

The key difference is in the number of threads allowed to access the critical section: locks restrict access to one thread at a time, whereas semaphores allow multiple threads (up to a fixed limit) to access the shared resource concurrently.

Semaphores can be used for controlling access to a pool of resources, while locks are more suited for situations where access to a single resource must be synchronized among threads.

• class Foo {
private Semaphore a = new Semaphore(1);
private Semaphore b = new Semaphore(0);
private Semaphore c = new Semaphore(0);

public Foo() {
}

public void first(Runnable printFirst) throws InterruptedException {
a.acquire(1);
// printFirst.run() outputs "first". Do not change or remove this line.
printFirst.run();
b.release(1);
}

public void second(Runnable printSecond) throws InterruptedException {
b.acquire(1);
// printSecond.run() outputs "second". Do not change or remove this line.
printSecond.run();
c.release(1);
}

public void third(Runnable printThird) throws InterruptedException {
c.acquire(1);
// printThird.run() outputs "third". Do not change or remove this line.
printThird.run();
a.release(1);
}
}

• class Foo {
private:
mutex m2, m3;

public:
Foo() {
m2.lock();
m3.lock();
}

void first(function<void()> printFirst) {
printFirst();
m2.unlock();
}

void second(function<void()> printSecond) {
m2.lock();
printSecond();
m3.unlock();
}

void third(function<void()> printThird) {
m3.lock();
printThird();
}
};

• from threading import Semaphore

class Foo:
def __init__(self):
self.semaphore1 = Semaphore(1)
self.semaphore2 = Semaphore(0)
self.semaphore3 = Semaphore(0)

def first(self, printFirst: 'Callable[[], None]') -> None:
self.semaphore1.acquire()
# printFirst() outputs "first". Do not change or remove this line.
printFirst()
self.semaphore2.release()

def second(self, printSecond: 'Callable[[], None]') -> None:
self.semaphore2.acquire()
# printSecond() outputs "second". Do not change or remove this line.
printSecond()
self.semaphore3.release()

def third(self, printThird: 'Callable[[], None]') -> None:
self.semaphore3.acquire()
# printThird() outputs "third". Do not change or remove this line.
printThird()
self.semaphore1.release()

#############

'''
A Barrier is a synchronization primitive that allows multiple threads
to wait until all threads have reached a certain point of execution before continuing.
'''

class Foo:
def __init__(self):
self.first_barrier = Barrier(2)
self.second_barrier = Barrier(2)

'''
first() self.first_barrier.wait(),  waits at self.first_barrier,
blocking until another thread also reaches that point.
Once both threads have reached the barrier,
they are released, and execution continues.
'''
def first(self, printFirst):
printFirst()
self.first_barrier.wait()

'''
The second method is similar.
It takes a function printSecond as a parameter.

It first waits at self.first_barrier, ensuring that the first method has completed.
Then, it calls printSecond to print the "second" output.

Finally, it waits at self.second_barrier,
blocking until the third method reaches the same point.
'''
def second(self, printSecond):
self.first_barrier.wait()
printSecond()
self.second_barrier.wait()

def third(self, printThird):
self.second_barrier.wait()
printThird()

############

class Foo:
def __init__(self):
self.done = (Event(),Event())

def first(self, printFirst):
printFirst()
self.done[0].set()

def second(self, printSecond):
self.done[0].wait()
printSecond()
self.done[1].set()

def third(self, printThird):
self.done[1].wait()
printThird()

############

class Foo:
def __init__(self):
self.gates = (Semaphore(0),Semaphore(0))

def first(self, printFirst):
printFirst()
self.gates[0].release()

def second(self, printSecond):
with self.gates[0]:
printSecond()
self.gates[1].release()

def third(self, printThird):
with self.gates[1]:
printThird()

############

class Foo:
def __init__(self):
self.exec_condition = Condition()
self.order = 0
self.first_finish = lambda: self.order == 1
self.second_finish = lambda: self.order == 2

def first(self, printFirst):
with self.exec_condition:
printFirst()
self.order = 1
self.exec_condition.notify(2)

def second(self, printSecond):
with self.exec_condition:
self.exec_condition.wait_for(self.first_finish)
printSecond()
self.order = 2
self.exec_condition.notify()

def third(self, printThird):
with self.exec_condition:
self.exec_condition.wait_for(self.second_finish)
printThird()

############

class Foo:
def __init__(self):
self.l2.acquire()
self.l3.acquire()

def first(self, printFirst: 'Callable[[], None]') -> None:
printFirst()
self.l2.release()

def second(self, printSecond: 'Callable[[], None]') -> None:
self.l2.acquire()
printSecond()
self.l3.release()

def third(self, printThird: 'Callable[[], None]') -> None:
self.l3.acquire()
printThird()