사전 동시성
개념

동시성

gabury1

여러 일이 겹친 시간 구간 안에서 함께 진행되는 성질입니다. 하나가 끝나야 다음이 시작되는 방식이 아닙니다. 그래서 두 일 사이의 순서가 하나로 정해지지 않습니다.

상세

한집에 사는 둘이 냉장고를 열어 보고 우유가 없으면 사 옵니다. 한 사람이 다녀오는 사이에 다른 사람이 냉장고를 열면 우유는 두 통이 됩니다. 다녀온 뒤에 열면 한 통입니다.

겹쳐 진행되면 따라오는 것이 순서입니다. 한 일 안에서는 앞뒤가 정해져 있습니다. 서로 다른 두 일의 동작 사이에는 그런 앞뒤가 없습니다. 그래서 겹쳐 진행된 동작 전체를 한 줄로 세울 수 없습니다. 어느 것이 먼저였는지 말할 수 있는 짝과 말할 수 없는 짝이 함께 남습니다. 순서가 하나로 정해지지 않는다는 것이 이 낱말의 본체입니다.

실행 단위가 몇 개인지는 정의에 들어가지 않습니다. 하나가 여러 일 사이를 오가도 됩니다. 여럿이 각각 붙어도 됩니다. 정해지지 않은 순서가 몇 가지로 실현될 수 있으니 같은 프로그램이 같은 입력에도 여러 결과를 낼 수 있습니다. 그 결과를 하나로 묶는 규율까지가 동시성을 다루는 일입니다.

배경

한 번에 한 가지 일만 붙들면 기다리는 시간이 통째로 버려집니다. 디스크나 네트워크에 읽기를 걸면 응답이 돌아올 때까지 시간이 걸립니다. 그동안 프로세서는 할 수 있는 다른 일이 있어도 못 합니다. 일감이 쌓여 있어도 줄은 하나뿐입니다.

그래서 기다리는 일과 지금 진행할 수 있는 일을 겹쳐 두게 됐습니다. 겹쳐 두는 순간 새 문제가 생깁니다. 두 일이 같은 것을 함께 만지면 서로 밟습니다. 그래서 어떤 구간에는 한쪽만 들어가게 막아야 합니다.

이 상황과 그것을 다루는 규율에 이름이 붙었습니다. 다익스트라는 1965년 강의노트에서 다루려는 문제 영역 자체가 둘 이상의 순차 프로세스 사이의 협력이라고 적었습니다. 같은 글은 한 순간에 많아야 한쪽만 들어가 있어야 하는 구간을 임계 구역이라 불렀습니다. 그것을 이루어 내는 일에는 상호 배제라는 이름을 붙였습니다. 순서 쪽은 램포트가 1978년 논문에서 정식화했습니다. 분산 시스템에서 한 사건이 다른 사건보다 먼저 일어난다는 개념이 사건들의 부분 순서를 정의한다는 것입니다. 그 위에서 서로 먼저 일어났다고 말할 수 없는 서로 다른 두 사건을 동시적이라고 정의했습니다. 동시성이 "함께 돈다"가 아니라 "순서를 정할 수 없다"로 적힌 자리입니다.

예시

POSIX 스레드의 뮤텍스 잠금

POSIX(Portable Operating System Interface) 표준은 pthread_mutex_lock() 함수를 이렇게 적습니다. 0 이나 [EOWNERDEAD] 를 반환하는 이 호출이 mutex 가 가리키는 뮤텍스 객체를 잠급니다. 뮤텍스가 이미 다른 스레드에 의해 잠겨 있으면 호출한 스레드는 뮤텍스가 사용 가능해질 때까지 블록됩니다. 겹쳐 도는 실행 단위들이 한 자원 앞에서 만나는 자리를 표준이 이렇게 적어 둔 것입니다.

PostgreSQL 의 동시성 제어

PostgreSQL 공식 문서는 PostgreSQL 이 데이터에 대한 동시 접근을 관리할 풍부한 도구 모음을 개발자에게 제공한다고 적습니다. 내부적으로 데이터 일관성은 다중 버전 모델로 유지됩니다. 이름은 다중 버전 동시성 제어(multiversion concurrency control, MVCC)입니다. SQL(Structured Query Language) 문 하나하나가 지금 바닥 상태와 무관하게 얼마 전 시점의 데이터 스냅샷을 봅니다. 같은 데이터 행을 갱신하는 동시 트랜잭션들이 만들어 낸 일관성 없는 데이터를 문이 보지 못하게 막는 방식입니다. 그렇게 데이터베이스 세션마다 트랜잭션 격리가 제공됩니다.

같은 문서는 SQL 표준이 네 가지 트랜잭션 격리 수준을 정의한다고 적습니다. 수준마다 금지되는 현상의 이름으로 dirty read · nonrepeatable read · phantom read · serialization anomaly 를 듭니다.

자바 카운터의 스레드 간섭

Java
class Counter {
    private int c = 0;
    public void increment() { c++; }
    public void decrement() { c--; }
    public int value() { return c; }
}

오라클 공식 자바 튜토리얼이 드는 조각입니다. 이 카운터는 increment 호출마다 c 에 1 을 더하도록 설계됐습니다. decrement 호출마다 1 을 빼도록 설계됐습니다. 다만 이 객체가 여러 스레드에서 참조되면 스레드 사이의 간섭이 기대대로 되는 것을 막을 수 있다고 문서는 적습니다.

c++ 한 줄이 세 단계로 나뉘기 때문입니다. c 의 현재 값을 읽습니다. 읽은 값에 1 을 더합니다. 더한 값을 c 에 다시 저장합니다. 스레드 A 가 increment 를 부르는 것과 거의 같은 시각에 스레드 B 가 decrement 를 부르면 동작이 이렇게 끼어들 수 있습니다.

sequenceDiagram
    participant 스레드A
    participant 카운터
    participant 스레드B
    스레드A->>카운터: c 읽기
    스레드B->>카운터: c 읽기
    Note over 스레드A: 읽은 값에 1 을 더함
    Note over 스레드B: 읽은 값에서 1 을 뺌
    스레드A->>카운터: 저장 · c 는 1
    스레드B->>카운터: 저장 · c 는 -1

c 의 처음 값이 0 이었다면 이렇게 됩니다. A 가 c 를 읽습니다. B 도 c 를 읽습니다. A 는 읽은 값에 1 을 더해 1 을 만듭니다. B 는 읽은 값에서 1 을 빼 -1 을 만듭니다. A 가 결과를 저장하면 c 는 1 이 됩니다. B 가 결과를 저장하면 c 는 -1 이 됩니다. A 의 결과가 B 에게 덮여 사라집니다. 문서는 이 끼어듦이 한 가지 가능성일 뿐이라고 적습니다. 다른 상황에서는 B 의 결과가 사라질 수도 있습니다. 아무 오류도 없을 수 있습니다. 겹쳐 돌 때 결과가 하나로 정해지지 않는다는 것이 여기서 그대로 보입니다.

경계

병렬성

코어가 하나뿐인 기계에서 두 일을 잘게 번갈아 돌리면 동시성인가요. 맞습니다. Go 프로젝트 공식 블로그는 롭 파이크의 발표를 이렇게 적습니다. 프로그래밍에서 동시성은 독립적으로 실행되는 프로세스들의 구성입니다. 병렬성은 아마도 서로 관련됐을 계산들을 동시에 실행하는 것입니다. 동시성은 한꺼번에 많은 일을 다루는 쪽에 관한 것입니다. 병렬성은 한꺼번에 많은 일을 하는 쪽에 관한 것입니다.

번갈아 돌리는 기계에서는 어느 한 순간에 실제로 진행되는 계산이 하나뿐입니다. 동시 실행은 없습니다. 그래도 두 일은 겹친 구간 안에 함께 놓여 있습니다. 그렇게 구성돼 있습니다. 구성은 성립하므로 동시성입니다. 코어 개수는 이 판정에 들어가지 않습니다.

비동기

결과를 나중에 받는 호출 하나만 있으면 그것으로 동시성인가요. 아닙니다. 호출의 생김새만으로는 정해지지 않습니다. POSIX 표준은 비동기 사건을 애플리케이션의 실행과 독립적으로 일어나는 사건이라고 정의합니다. 같은 표준은 비동기 입출력의 완료를 따로 정의합니다. 대응하는 동기 읽기나 쓰기였다면 완료됐을 시점, 그리고 딸린 상태 필드들이 갱신된 시점입니다. 완료 시점이 호출과 별도로 적히는 자리입니다. 부른 쪽이 그 사이에 다른 일을 진행하면 두 일이 겹친 구간을 갖습니다. 그때가 동시성입니다. 부른 쪽이 결과만 기다린다면 겹치는 구간이 없습니다.

스레드

스레드는 이 성질 자체인가요, 그것을 얻는 수단인가요. 수단입니다. POSIX 표준은 스레드를 한 프로세스 안의 단일한 제어 흐름이라고 정의합니다. 제어 흐름 하나를 가리키는 이름입니다. 같은 표준은 스레드 세이프한 함수를 이렇게 정의합니다. 여러 스레드가 같은 함수에 대한 다른 호출들과 동시에 불러도 안전하게 불릴 수 있는 함수입니다. 겹쳐 불릴 수 있다는 조건이 스레드가 아니라 함수 쪽에 붙어 있습니다. 겹침은 스레드를 여럿 놓았을 때 생기는 것입니다.

반대쪽에서도 확인됩니다. 실행 단위가 하나여도 동시성은 성립합니다. Node.js 공식 문서는 이렇게 적습니다. 기본적으로 쓰이는 자바스크립트 스레드는 하나뿐입니다. 그런데도 이벤트 루프가 논블로킹 입출력을 수행하게 해 줍니다.

관련 항목

겹쳐 도는 실행 단위

프로세스 · 스레드 · 코루틴 · 태스크 · 이벤트 루프 · 고루틴

서로 밟지 않게 막는 수단

임계 구역 · 상호 배제 · 뮤텍스 · 세마포어 · 원자적 연산 · compare-and-set · 메모리 모델 · happens-before

겹쳐 도는 단위가 정보를 나누는 방식

채널 · 메시지 전달 · 액터 모델 · 공유 메모리

자주 나는 오류·장애

경쟁 상태 · 데드락 · 라이브락 · 기아 · ABA 문제

헷갈리는 이웃

병렬성 · 비동기 · 스레드

동시성을 실제로 다루는 기법

문맥 교환 · 격리 수준 · 낙관적 잠금 · 다중 버전 동시성 제어

동시성을 실제로 다루는 제품·표준

PostgreSQL · Java · Node.js · POSIX · Go

다른 이름: concurrency · 병행성