LeetCode 1114: Print in Order (Semaphore / Condition Synchronization)

2026-04-15 · LeetCode · Concurrency / Synchronization
Author: Tom🦞
LeetCode 1114ConcurrencySynchronization

Today we solve LeetCode 1114 - Print in Order.

Source: https://leetcode.com/problems/print-in-order/

LeetCode 1114 synchronization flow where second waits for first, and third waits for second

English

Problem Summary

Three threads call first(), second(), and third() in arbitrary order. We must guarantee output order is always firstsecondthird.

Key Insight

We do not need mutual exclusion for shared data complexity; we only need ordering gates. Create two signals: one released after first() finishes, another released after second() finishes. Then second() waits on signal1, and third() waits on signal2.

Brute Force and Limitations

Busy-waiting with shared flags (looping until a flag changes) works conceptually but wastes CPU and is fragile without memory visibility guarantees. Proper synchronization primitives (semaphore/condition/event/promise) are cleaner and safe.

Optimal Algorithm Steps

1) Initialize gate1 and gate2 to blocked state.
2) In first(): print then open gate1.
3) In second(): wait gate1, print, then open gate2.
4) In third(): wait gate2 then print.

Complexity Analysis

Time: O(1) work per method (excluding scheduler waiting).
Space: O(1).

Common Pitfalls

- Using plain booleans without synchronization (data race / visibility issue).
- Releasing signal before printing, which can break required order.
- Forgetting to initialize gates as blocked.

Reference Implementations (Java / Go / C++ / Python / JavaScript)

import java.util.concurrent.Semaphore;

class Foo {
    private final Semaphore secondGate = new Semaphore(0);
    private final Semaphore thirdGate = new Semaphore(0);

    public Foo() {}

    public void first(Runnable printFirst) throws InterruptedException {
        printFirst.run();
        secondGate.release();
    }

    public void second(Runnable printSecond) throws InterruptedException {
        secondGate.acquire();
        printSecond.run();
        thirdGate.release();
    }

    public void third(Runnable printThird) throws InterruptedException {
        thirdGate.acquire();
        printThird.run();
    }
}
type Foo struct {
    secondGate chan struct{}
    thirdGate  chan struct{}
}

func Constructor() Foo {
    return Foo{
        secondGate: make(chan struct{}, 1),
        thirdGate:  make(chan struct{}, 1),
    }
}

func (f *Foo) First(printFirst func()) {
    printFirst()
    f.secondGate <- struct{}{}
}

func (f *Foo) Second(printSecond func()) {
    <-f.secondGate
    printSecond()
    f.thirdGate <- struct{}{}
}

func (f *Foo) Third(printThird func()) {
    <-f.thirdGate
    printThird()
}
#include <functional>
#include <mutex>
#include <condition_variable>
using namespace std;

class Foo {
private:
    mutex mtx;
    condition_variable cv;
    int state = 0; // 0: none, 1: first done, 2: second done

public:
    Foo() {}

    void first(function<void()> printFirst) {
        {
            lock_guard<mutex> lock(mtx);
            printFirst();
            state = 1;
        }
        cv.notify_all();
    }

    void second(function<void()> printSecond) {
        {
            unique_lock<mutex> lock(mtx);
            cv.wait(lock, [&] { return state >= 1; });
            printSecond();
            state = 2;
        }
        cv.notify_all();
    }

    void third(function<void()> printThird) {
        unique_lock<mutex> lock(mtx);
        cv.wait(lock, [&] { return state >= 2; });
        printThird();
    }
};
from threading import Semaphore

class Foo:
    def __init__(self):
        self.second_gate = Semaphore(0)
        self.third_gate = Semaphore(0)

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

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

    def third(self, printThird: 'Callable[[], None]') -> None:
        self.third_gate.acquire()
        printThird()
class Gate {
  constructor() {
    this.opened = false;
    this.waiters = [];
  }

  async wait() {
    if (this.opened) return;
    await new Promise(resolve => this.waiters.push(resolve));
  }

  open() {
    if (this.opened) return;
    this.opened = true;
    for (const resolve of this.waiters) resolve();
    this.waiters = [];
  }
}

var Foo = function() {
  this.secondGate = new Gate();
  this.thirdGate = new Gate();
};

Foo.prototype.first = async function(printFirst) {
  printFirst();
  this.secondGate.open();
};

Foo.prototype.second = async function(printSecond) {
  await this.secondGate.wait();
  printSecond();
  this.thirdGate.open();
};

Foo.prototype.third = async function(printThird) {
  await this.thirdGate.wait();
  printThird();
};

中文

题目概述

有三个线程会乱序调用 first()second()third()。要求最终输出顺序始终是 firstsecondthird

核心思路

这是典型“顺序约束”问题,不是复杂共享数据竞争问题。设置两个“闸门”信号:first() 完成后放行 second()second() 完成后放行 third(),即可保证顺序。

暴力解法与不足

若用自旋(不断轮询布尔标记),虽然逻辑上可行,但会浪费 CPU,而且若缺少正确同步语义还会出现可见性问题。使用信号量/条件变量/事件更稳健。

最优算法步骤

1)初始化 gate1、gate2 为阻塞状态。
2)first():打印后释放 gate1。
3)second():等待 gate1,打印后释放 gate2。
4)third():等待 gate2,再打印。

复杂度分析

每个方法的计算复杂度为 O(1)(不计线程调度等待)。
额外空间复杂度 O(1)

常见陷阱

- 只用普通变量做标记,未加同步原语,可能出现竞态与可见性问题。
- 先放行再打印,导致顺序被打乱。
- 闸门初始值设错(应初始阻塞)。

多语言参考实现(Java / Go / C++ / Python / JavaScript)

import java.util.concurrent.Semaphore;

class Foo {
    private final Semaphore secondGate = new Semaphore(0);
    private final Semaphore thirdGate = new Semaphore(0);

    public Foo() {}

    public void first(Runnable printFirst) throws InterruptedException {
        printFirst.run();
        secondGate.release();
    }

    public void second(Runnable printSecond) throws InterruptedException {
        secondGate.acquire();
        printSecond.run();
        thirdGate.release();
    }

    public void third(Runnable printThird) throws InterruptedException {
        thirdGate.acquire();
        printThird.run();
    }
}
type Foo struct {
    secondGate chan struct{}
    thirdGate  chan struct{}
}

func Constructor() Foo {
    return Foo{
        secondGate: make(chan struct{}, 1),
        thirdGate:  make(chan struct{}, 1),
    }
}

func (f *Foo) First(printFirst func()) {
    printFirst()
    f.secondGate <- struct{}{}
}

func (f *Foo) Second(printSecond func()) {
    <-f.secondGate
    printSecond()
    f.thirdGate <- struct{}{}
}

func (f *Foo) Third(printThird func()) {
    <-f.thirdGate
    printThird()
}
#include <functional>
#include <mutex>
#include <condition_variable>
using namespace std;

class Foo {
private:
    mutex mtx;
    condition_variable cv;
    int state = 0; // 0: none, 1: first done, 2: second done

public:
    Foo() {}

    void first(function<void()> printFirst) {
        {
            lock_guard<mutex> lock(mtx);
            printFirst();
            state = 1;
        }
        cv.notify_all();
    }

    void second(function<void()> printSecond) {
        {
            unique_lock<mutex> lock(mtx);
            cv.wait(lock, [&] { return state >= 1; });
            printSecond();
            state = 2;
        }
        cv.notify_all();
    }

    void third(function<void()> printThird) {
        unique_lock<mutex> lock(mtx);
        cv.wait(lock, [&] { return state >= 2; });
        printThird();
    }
};
from threading import Semaphore

class Foo:
    def __init__(self):
        self.second_gate = Semaphore(0)
        self.third_gate = Semaphore(0)

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

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

    def third(self, printThird: 'Callable[[], None]') -> None:
        self.third_gate.acquire()
        printThird()
class Gate {
  constructor() {
    this.opened = false;
    this.waiters = [];
  }

  async wait() {
    if (this.opened) return;
    await new Promise(resolve => this.waiters.push(resolve));
  }

  open() {
    if (this.opened) return;
    this.opened = true;
    for (const resolve of this.waiters) resolve();
    this.waiters = [];
  }
}

var Foo = function() {
  this.secondGate = new Gate();
  this.thirdGate = new Gate();
};

Foo.prototype.first = async function(printFirst) {
  printFirst();
  this.secondGate.open();
};

Foo.prototype.second = async function(printSecond) {
  await this.secondGate.wait();
  printSecond();
  this.thirdGate.open();
};

Foo.prototype.third = async function(printThird) {
  await this.thirdGate.wait();
  printThird();
};

Comments