정의
경쟁 상태(race condition)는 여러 실행 흐름이 거의 같은 시점에 같은 공유 자원에 접근해 그중 적어도 하나가 자원을 갱신할 때, 최종 결과가 접근이 겹치는 순서에 따라 달라지는 상태다.[1]
멀티태스킹 커널 위에서 도는 코드는 자기가 언제 선점될지 알 수 없다. 선점 시점이 코드의 통제 밖이므로, 여러 태스크가 같은 데이터를 건드리면 어느 명령 사이에서 실행이 갈리느냐에 따라 남는 값이 달라진다. 결과가 스케줄링 타이밍에 좌우된다는 점에서 경쟁 상태는 RTOS 위에 올라가는 응용이 가장 먼저 마주치는 무결성 문제다.
이 페이지는 현상 자체를 다룬다. 어느 코드 구간을 보호 대상으로 잡을지는 임계 구역에서, 그 구간을 실제로 잠그는 수단은 락에서, 보호를 도입한 뒤 우선순위 사이에 생기는 부작용은 우선순위 역전에서 다룬다.
원자적이지 않은 갱신
전역 카운터를 1 증가시키는 한 줄은 기계어 수준에서 세 명령으로 쪼개진다. 메모리의 값을 레지스터로 읽고, 레지스터를 1 증가시키고, 결과를 다시 메모리에 쓴다. 하드웨어가 중단 없이 실행된다고 보장하는 단위는 명령 하나이지 이 세 명령의 묶음이 아니다. 어떤 동작이 원자적(atomic)이라는 말은 전부 일어나거나 전혀 일어나지 않은 것처럼 보이고 중간 상태가 관측되지 않는다는 뜻인데, 세 명령으로 나뉜 갱신은 그 조건을 만족하지 못한다.[1]
카운터가 50인 상태에서 두 실행 흐름이 각각 한 번씩 증가시키면 결과는 52여야 한다. 그런데 첫 흐름이 값을 읽고 증가시킨 직후 타이머 인터럽트로 문맥이 전환되면, 둘째 흐름은 아직 갱신되지 않은 50을 읽어 51을 쓰고, 되돌아온 첫 흐름은 자기 레지스터에 남아 있던 51을 그대로 덮어쓴다. 증가 코드는 두 번 실행됐지만 카운터는 51에서 멈추고, 한 번의 갱신이 사라진다.
공유 자원
경쟁의 대상이 되는 것은 변수만이 아니다. 임계 구역은 공유 자원에 접근하는 코드 구간이고, 그 자원은 대개 변수나 자료구조지만 파일처럼 커널이 관리하는 대상도 마찬가지다. 두 프로세스가 같은 파일 끝에 데이터를 덧붙이면 각자 새 블록을 할당하고 파일의 inode에 블록 위치를 기록하고 파일 크기를 갱신해야 하는데, 이 갱신들 사이에 인터럽트가 끼어들 수 있으므로 할당 비트맵과 inode를 만지는 코드가 곧 임계 구역이 된다.[1]
임베디드 제어기에서는 목록이 더 늘어난다. OSEK/VDX OS는 자원 관리가 조정하는 대상으로 스케줄러 같은 관리 개체, 프로그램 구간, 메모리 영역, 하드웨어 영역을 든다.[2] 주변 장치 레지스터를 읽고 고쳐 쓰는 코드도 전역 변수를 증가시키는 코드와 같은 이유로 경쟁의 대상이다.
비결정적 결과
경쟁 상태가 하나라도 있는 프로그램은 비결정적(indeterminate)이 된다. 출력이 실행마다 달라지고, 어느 흐름이 언제 실행됐는지에 따라 결과가 갈린다.[1] 결함이 특정한 문맥 전환 타이밍에서만 드러나므로 같은 입력으로 다시 돌려도 재현되지 않는 일이 흔하고, 시험을 통과했다는 사실이 정확성의 근거가 되지 못한다.
교착이 아닌 동시성 결함