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:
numsis a permutation of[1, 2, 3].
Submitted Code
from threading import Event
class Foo:
def __init__(self):
self.first_done = Event()
self.second_done = Event()
def first(self, printFirst: 'Callable[[], None]') -> None:
printFirst() # printFirst() outputs "first". Do not change or remove this line.
self.first_done.set() # first 끝났다고 알림
def second(self, printSecond: 'Callable[[], None]') -> None:
self.first_done.wait() # first 끝날 때까지 대기
printSecond() # printSecond() outputs "second". Do not change or remove this line.
self.second_done.set() # second 끝났다고 알림
def third(self, printThird: 'Callable[[], None]') -> None:
self.second_done.wait() # second 끝날 때까지 대기
printThird() # printThird() outputs "third". Do not change or remove this line.
Runtime: 57 ms | Beats 34.40%
Memory: 17.70 MB | Beats 99.63%
Event를 이용하는 방법으로, 두 번째 스레드는 첫 번째 스레드가 이벤트를 set할 때까지 wait하고, 세 번째 스레드는 두 번째 스레드가 이벤트를 set할 때까지 wait한다.
Other Solutions
1st
from threading import Barrier
class Foo:
def __init__(self):
# Barrier(2): 스레드 2개가 해당 barrier의 wait()에 도달해야 통과
self.first_barrier = Barrier(2)
self.second_barrier = Barrier(2)
def first(self, printFirst):
printFirst()
self.first_barrier.wait() # second도 wait()에 도착할 때까지 대기
def second(self, printSecond):
self.first_barrier.wait() # first가 끝날 때까지 대기
printSecond()
self.second_barrier.wait() # third도 wait()에 도착할 때까지 대기
def third(self, printThird):
self.second_barrier.wait() # second가 끝날 때까지 대기
printThird()
Barrier는 정해진 수의 스레드가 모두 barrier에 도착할 때까지 대기시키고, 마지막 스레드가 도착하는 순간 그 그룹을 동시에 통과시키는 방식이다.
from threading import Lock
class Foo:
def __init__(self):
# 두 개의 lock을 준비 후 바로 실행하지 못하도록 미리 잠가 둔다
self.locks = (Lock(),Lock())
self.locks[0].acquire()
self.locks[1].acquire()
def first(self, printFirst):
printFirst()
self.locks[0].release() # first 종료 후 second가 들어갈 수 있도록 잠금 해제
def second(self, printSecond):
with self.locks[0]: # 첫 번째 lock이 열릴 때까지 대기
printSecond()
self.locks[1].release() # second 종료 후 third가 들어갈 수 있도록 잠금 해제
def third(self, printThird):
with self.locks[1]: # 두 번째 lock이 열릴 때까지 대기
printThird()
Lock은 원래 임계 구역 보호용이지만, 여기서는 한 번에 한 스레드만 실행할 수 있도록 순서를 강제하는 용도로 사용되었다.
from threading import Semaphore
class Foo:
def __init__(self):
# Semaphore(0): 카운터 없음 -> 대기
self.gates = (Semaphore(0),Semaphore(0))
def first(self, printFirst):
printFirst()
self.gates[0].release() # second용 카운터 +1 -> second 통과 가능
def second(self, printSecond):
with self.gates[0]: # first에서 카운팅 될 때까지 대기
printSecond()
self.gates[1].release() # third용 카운터 +1 -> third 통과 가능
def third(self, printThird):
with self.gates[1]: # second에서 카운팅 될 때까지 대기
printThird()
Semaphore는 Event와 비슷하나 카운터 숫자로 순서를 관리할 수 있다.
from threading import Condition
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: # order == 1 될 때까지 대기
self.exec_condition.wait_for(self.first_finish)
printSecond()
self.order = 2
self.exec_condition.notify()
def third(self, printThird):
with self.exec_condition: # order == 2 될 때까지 대기
self.exec_condition.wait_for(self.second_finish)
printThird()
Condition은 Lock을 기반으로 조건이 만족될 때까지 wait()하며, 다른 스레드의 notify() 신호에 의해 다시 깨어나 조건을 재검사하는 방식이다.