CyclicBarrier — 주기적 배리어
CyclicBarrier — 주기적 배리어
CyclicBarrier는 일정 수의 스레드가 모두 공통의 배리어 지점에 도달할 때까지 서로를 기다리게 하는 동기화 보조 장치예요. 고정 크기의 스레드 파티(party)가 주기적으로 서로를 기다려야 하는 프로그램에 유용해요. 대기 스레드가 해제된 뒤 재사용할 수 있어 "cyclic"(주기적)이라 불러요.
본문
개념 이해하기
CyclicBarrier는 일정 수의 스레드가 모두 공통 배리어 지점에 도달할 때까지 서로 기다리게 해요. 배리어가 trip되면(모든 파티가 도착하면) 대기 스레드가 해제되고 재사용될 수 있어요.
public class CyclicBarrier
extends Object
선택적으로 Runnable 커맨드(barrier action)를 지원하는데, 마지막 스레드가 도착한 뒤 어떤 스레드도 해제되기 전에 각 배리어 지점마다 한 번 실행돼요. 파티들이 계속 진행하기 전에 공유 상태를 갱신할 때 유용해요.
활용 예 — 병렬 분해
class Solver {
final int N;
final float[][] data;
final CyclicBarrier barrier;
class Worker implements Runnable {
int myRow;
Worker(int row) { myRow = row; }
public void run() {
while (!done()) {
processRow(myRow);
try {
barrier.await();
} catch (InterruptedException ex) { return; }
catch (BrokenBarrierException ex) { return; }
}
}
}
public Solver(float[][] matrix) {
data = matrix;
N = matrix.length;
Runnable barrierAction = () -> mergeRows(...);
barrier = new CyclicBarrier(N, barrierAction);
List<Thread> threads = new ArrayList<>(N);
for (int i = 0; i < N; i++) {
Thread thread = new Thread(new Worker(i));
threads.add(thread);
thread.start();
}
for (Thread thread : threads)
try { thread.join(); } catch (InterruptedException ex) { }
}
}
여기서 각 워커는 행렬의 한 행을 처리한 뒤 모든 행이 처리될 때까지 배리어에서 기다려요. 모든 행이 처리되면 Runnable 배리어 액션(행 병합)이 실행돼요.
배리어 액션이 파티의 일시정지에 의존하지 않는다면, 해제 시 어떤 스레드든 그 액션을 실행할 수 있어요. 이를 돕기 위해 각 await() 호출이 그 스레드의 도착 인덱스(arrival index) 를 반환해요. 예:
if (barrier.await() == 0) {
// log the completion of this iteration
}
CyclicBarrier는 실패한 동기화 시도에 대해 전부-아니면-전무(all-or-none) 파손 모델을 사용해요. 한 스레드가 인터럽트·실패·타임아웃으로 배리어 지점을 일찍 떠나면, 그 지점에서 기다리던 모든 다른 스레드도 BrokenBarrierException(또는 같은 시각 인터럽트됐으면 InterruptedException)으로 비정상적으로 떠나요.
메모리 일관성 효과: await() 호출 전의 동작은 배리어 액션의 일부인 동작보다 happen-before이고, 이는 다시 다른 스레드의 대응하는 await() 성공적 반환에 뒤따르는 동작보다 happen-before예요.
생성자
public CyclicBarrier(int parties, Runnable barrierAction) — 주어진 수의 파티가 기다릴 때 trip되고, trip 시 마지막으로 들어온 스레드가 배리어 액션을 실행하는 새 CyclicBarrier를 만들어요.
IllegalArgumentException—parties가 1보다 작을 때
public CyclicBarrier(int parties) — trip될 때 사전 정의된 액션을 수행하지 않는 새 배리어를 만들어요.
IllegalArgumentException
메서드
public int getParties() — 이 배리어를 trip시키는 데 필요한 파티 수를 반환해요.
public int await() throws InterruptedException, BrokenBarrierException — 모든 파티가 이 배리어에서 await를 호출할 때까지 기다려요. 마지막으로 도착한 스레드가 아니면, 마지막 스레드가 도착하거나, 인터럽트되거나, 다른 대기 스레드가 인터럽트되거나, 타임아웃되거나, reset()이 호출될 때까지 대기 상태가 돼요. 마지막 스레드(반환 인덱스 0)는 다른 스레드를 진행시키기 전에 배리어 액션을 실행해요.
- 반환: 현재 스레드의 도착 인덱스 —
getParties() - 1이 첫 도착,0이 마지막 도착 InterruptedException,BrokenBarrierException
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException — 모든 파티가 await를 호출하거나 지정 대기 시간이 지날 때까지 기다려요. 시간이 지나면 TimeoutException이 던져지고 배리어는 파손돼요.
public boolean isBroken() — 생성 또는 마지막 reset 이후 하나 이상의 파티가 인터럽트·타임아웃으로 배리어를 떠났거나 배리어 액션이 예외로 실패했으면 배리어가 파손 상태인지 여부를 반환해요.
public void reset() — 배리어를 초기 상태로 재설정해요. 현재 기다리는 파티가 있으면 BrokenBarrierException으로 반환돼요. 재설정은 복잡할 수 있으므로 이후 사용에는 새 배리어를 만드는 게 선호될 수 있어요.
public int getNumberWaiting() — 현재 배리어에서 기다리는 파티 수를 반환해요. 주로 디버깅·단언(assertion)용.