공유 메모리를 사용할 때의 함정과 베스트 프랙티스 - 동기, 가시성, 수명, ABI, 보안을 먼저 정리

· · Shared Memory, IPC, Concurrency, C++, C#, Windows 개발

이미지 프레임, 검사 결과, 시계열 로그, 판정보, 거대 버퍼. 동일 머신 내에서 큰 데이터를 저 레이턴시로 주고받고 싶을 때, 공유 메모리는 꽤 매력적입니다.

다만, 여기서 약간 위험한 것은 공유 메모리가 「빠른 IPC」라는 얼굴로 다가오는 것입니다. 실제로는 공유 메모리는 「복사를 줄일 수 있는 대신에 정합성의 책임을 앱 측으로 밀어 되돌리는 IPC」입니다.

  • 빠르다
  • 유연하다
  • 하지만 protocol은 자체 제작
  • 사고나면 증상이 화려

대개 이 4점 세트입니다.

이 글에서는 Windows의 file mapping과 POSIX shm_open / mmap을 염두에 두고, 공유 메모리를 실무에서 사용할 때의 막힘 포인트와 사고율을 내리는 설계를 정리합니다. C/C++나 C#의 MemoryMappedFile이나 본질은 거의 같습니다.1

1. 먼저 결론(한마디로)

먼저 꽤 거칠지만 실무에서 도움이 되는 말로 하면 이렇습니다.

  • 공유 메모리는 같은 바이트 열을 복수 프로세스에서 보여주는 구조이며, 동기 그 자체가 아닙니다23
  • 빠른 것은 큰 데이터를 동일 머신 내에서 주고받을 때입니다. 작은 제어 메시지만이라면 pipe / socket / named pipe / queue 쪽이 편한 경우가 꽤 많습니다
  • 공유 메모리에서는 보이는 것안전하게 읽을 수 있는 것이 별개 문제입니다
  • volatile은 설계의 토대로 하지 않는 편이 좋습니다. 원자성, 순서, 대기는 따로 생각합니다45
  • 생 포인터, HANDLE, file descriptor, std::string, std::vector, std::mutex를 그대로 두면 대개 나중에 웁니다
  • 공유 메모리에 두는 데이터는 고정 폭 정수 + 명시적 레이아웃 + 버전 있는 헤더로 기울이는 편이 안전합니다
  • 선두 헤더에 magic / version / size / state / generation / heartbeat를 두는 것만으로도 사고 조사의 쉬움이 꽤 바뀝니다
  • 공유 메모리의 난관은 속도가 아니라 초기화, 수명, 복구, 권한, ABI입니다
  • Windows라면 CreateFileMapping / OpenFileMapping / MapViewOfFile, POSIX라면 shm_open / ftruncate / mmap가 골격입니다63
  • 가장 사고 나기 어려운 것은 SPSC(single-producer single-consumer)의 링 버퍼더블 버퍼부터 시작하는 것입니다

요컨대 공유 메모리는 빠르지만, 거칠게 사용하면 「마음대로 동기되고 있는 기분이 드는 병」에 걸린다. 여기를 피하는 것이 최초의 승부입니다.

2. 공유 메모리는 무엇을 공유하고, 무엇을 공유하지 않는가

공유 메모리는 대략 말하면 같은 물리 페이지를 복수 프로세스의 가상 주소 공간에 매핑하는 구조입니다. Windows에서는 file mapping object와 view를 사용하고, POSIX에서는 shared memory object를 mmap합니다.273

여기서 중요한 것은 2점입니다.

  1. 공유되는 것은 내용의 바이트 열이며, 가상 주소 그 자체가 아니다
  2. coherent이라는 것동기되고 있다는 것은 별개

Windows의 문서에서도 같은 file mapping object에서 만든 view는 동시점에서 coherent라고 되어 있습니다. 다만, 그것은 독자가 항상 일관된 갱신 완료 레코드를 읽을 수 있다는 의미가 아닙니다.8

예를 들어,

  • writer가 length
  • 이어서 payload
  • 이어서 ready flag

순서로 쓸 생각이어도, reader 측이 아무 동기도 없이 읽으면 새로운 length와 오래된 payload를 조합해 보는 경우가 있습니다. 공유 메모리는 여기를 자동으로 고쳐주지 않습니다.

즉, 공유 메모리가 공유하는 것은 바이트. 공유하지 않는 것은 의미, 순서, 완료 통지, 복구 방침입니다. 이 부근은 전부 이쪽에서 설계할 필요가 있습니다.

3. 공유 메모리가 어울리는 장면 / 어울리지 않는 장면

장면 어울림・어울리지 않음 이유
동일 머신 내에서 큰 프레임이나 버퍼를 넘긴다 어울린다 복사 횟수를 줄이기 쉽다
고빈도 센서값, 이미지, 음성, 판정보 등 어울린다 저 레이턴시・고 스루풋을 노리기 쉽다
작은 커맨드나 응답만을 주고받는다 별로 어울리지 않는다 제어를 위한 동기 비용이 상대적으로 무겁다
다른 머신과 주고받는다 어울리지 않는다 공유 메모리는 기본적으로 동일 호스트 전제
다른 언어・다른 버전이 장기 공존한다 어렵다 ABI와 버저닝 설계가 필요
영속화도 필요 목적 나름 file-backed mapping은 유력하지만 영속화와 IPC의 책무가 섞이기 쉽다

실무에서는 제어는 메시지계, 데이터 본체는 공유 메모리라는 분리가 꽤 강합니다. 예를 들어,

  • UI 프로세스 → worker 프로세스로 「다음 프레임을 사용해」라고 통지하는 것은 event / pipe / socket
  • 실제 프레임 본체는 공유 메모리

라는 구성입니다. 이것이 꽤 평화롭습니다.

4. 처음에 결정해야 할 4가지

공유 메모리를 설계할 때 처음에 결정해야 할 것은 다음 4가지입니다.

4.1 control plane과 data plane을 나눈다

무엇을 shared memory에 둘지 먼저 결정합니다.

  • data plane: 이미지, 음성, 레코드 열, 벌크 데이터
  • control plane: 시작, 정지, 에러, 재접속, 재초기화, 통지

이 2가지를 나누는 것만으로도 shared memory 측의 설계가 꽤 단순해집니다.

4.2 병렬 모델을 좁힌다

  • SPSC: 1 producer / 1 consumer
  • MPSC: 다 writer / 1 consumer
  • SPMC: 1 writer / 다 reader
  • MPMC: 다 writer / 다 reader

난이도는 대체로 이 순서로 올라갑니다. 처음부터 MPMC로 가는 것은 꽤 용맹합니다. 대개 나중에 메모리 순서의 요괴가 나옵니다.

4.3 소유자와 수명을 결정한다

  • 누가 만드는가
  • 누가 초기화하는가
  • 누가 지우는가
  • 참가자가 도중에 떨어졌을 때, 누가 회복시키는가

여기가 애매하면 기동 순이나 재기동할 때마다 공기가 탁해집니다.

4.4 ABI와 버전을 결정한다

  • 레이아웃
  • 형 크기
  • alignment
  • reserved 영역
  • version / feature flags
  • 호환성의 유무

shared memory는 API가 아니라 ABI(binary interface)의 이야기입니다. 여기를 거칠게 하면 소스 호환은 있는데 실행 시만 망가지는 싫은 사고가 됩니다.

5. 자주 있는 함정

5.1 동기하지 않는다

가장 많은 것은 이것입니다.

「같은 메모리를 보고 있으니까 쓰면 읽을 수 있을 것」

읽을 수 있는 경우는 있습니다. 하지만 그것은 올바른 타이밍에, 올바른 단위로, 올바른 순서로 읽을 수 있다는 것을 의미하지 않습니다.

Windows에서도 POSIX에서도 공유 메모리에의 액세스는 별도의 동기 수단과 조합하는 전제입니다. Windows의 설명에서도 공유 view에의 액세스는 mutex / semaphore / event 등으로 협조하도록 쓰여 있습니다.2 POSIX의 설명에서도 shared memory에의 액세스는 동기가 필요합니다.9

5.2 volatile로 어떻게든 하려고 한다

volatile은 공유 메모리 설계를 구해주는 마법이 아닙니다. 적어도 atomicitymutual exclusion은 별개 문제입니다.45

예를 들어 volatile bool ready;를 두고 busy loop하는 설계는,

  • CPU를 쓸데없이 사용한다
  • payload와 ready의 순서 보증이 애매해진다
  • portable이 아니다
  • 도중 상태를 줍기 쉽다

와, 대개 좋은 것이 없습니다.

게다가 Windows의 WaitOnAddress같은 프로세스 내의 thread 용입니다. cross-process의 대기 기구로서는 생각하지 않는 편이 안전합니다.10

5.3 도중 상태를 읽게 한다

공유 메모리에서 사고 날 때의 외관은 꽤 평범합니다.

  • 헤더만 새롭다
  • 페이로드만 오래됐다
  • 길이만 갱신 완료
  • 2개의 필드 조합이 망가져 있다

단일 scalar를 atomic으로 갱신하는 것만이라면 이야기는 비교적 단순하지만, 복수 필드로 이루어진 레코드를 공개한다면 commit 순서가 필요합니다.

전형적으로는 다음 중 어느 것입니다.

  • mutex로 통째로 지킨다
  • 더블 버퍼로 해 마지막에 「지금의 유효 버퍼 번호」를 전환한다
  • 링 버퍼로 해 slot별로 state / sequence를 가진다
  • 1 writer / 다 reader라면 sequence counter로 snapshot을 취한다

「마지막에 ready flag를 세운다」만으로도, 그 flag를 어떤 메모리 순서로 쓸지 / 읽을지를 정하지 않으면 설계로서는 아직 무릅니다. 공유 메모리에서는 공개 타이밍 그 자체가 프로토콜입니다.

5.4 포인터나 복잡 오브젝트를 그대로 둔다

이것은 꽤 빈출입니다.

  • 생 포인터
  • HANDLE
  • file descriptor
  • std::string
  • std::vector
  • std::unordered_map
  • std::mutex
  • CRITICAL_SECTION

이 부근을 shared memory에 그대로 두고, 별도 프로세스에서 사용하려고 하는 녀석입니다. 대개 작은 지옥이 시작됩니다.

이유는 단순해서 가상 주소나 process-local한 자원은 그 process의 문맥에만 의미가 있기 때문입니다. Windows의 view도 같은 mapping을 별도 process에서 map해도 가상 주소가 일치한다고는 한정되지 않습니다.711

그래서 참조가 필요하다면 베이스 주소로부터의 offset으로 가지는 것이 기본입니다.

typedef struct ShmRef {
    uint64_t offset;   // 세그먼트 선두에서의 상대 위치
    uint32_t length;
    uint32_t kind;
} ShmRef;

이것이라면 각 process가 base + offset으로 자신의 주소로 변환할 수 있습니다.

5.5 ABI가 망가진다

shared memory는 소스 코드가 아니라 바이너리의 약속입니다. 즉, 다음의 차이가 전부 효과적입니다.

  • int / long의 크기
  • bool의 표현
  • enum의 underlying type
  • wchar_t의 크기
  • 32bit / 64bit의 차
  • #pragma pack
  • compiler / language의 차이
  • alignment / padding
  • little-endian / big-endian

동일 호스트 내라면 endianness는 맞춰져 있는 경우가 많지만, ARM64 대응이나 mixed toolchain이 들어가는 것만으로도 꽤 평범하게 어긋납니다.

그래서 shared memory에 두는 구조는 다음을 강하게 권합니다.

  • uint32_t / uint64_t 등의 고정 폭 정수
  • 명시적인 padding / reserved
  • header에 version, header_size, record_size, total_size
  • 필요하면 static_assert(sizeof(...))
  • non-trivial object를 두지 않는다

5.6 초기화 레이스

shared memory는 「만든 측이 초기화했을 것」이라는 사고방식으로 망가지기 쉽습니다.

Windows에서는 CreateFileMapping이 기존 이름에 해당하면 기존 오브젝트를 반환하고, GetLastError()ERROR_ALREADY_EXISTS를 알 수 있습니다. pagefile-backed한 mapping의 초기 페이지는 0으로 시작됩니다.8 POSIX에서는 새로운 shared memory object는 처음은 길이 0이고, ftruncate로 크기를 붙입니다. 새롭게 확보된 바이트는 0 초기화입니다. O_CREAT | O_EXCL에 의한 create는 원자적입니다.3

이 차이를 모른 채,

  • open했으면 즉시 사용
  • 초기화 완료 플래그가 없다
  • 참가자가 동시에 초기화한다
  • version mismatch를 보지 않는다

고 하면 기동 순 여하에 따라 망가집니다.

최저한 선두 헤더에 다음 state를 두는 편이 좋습니다.

  • INITIALIZING
  • READY
  • BROKEN

그리고 creator만이 초기화하고, joiner는 READY를 기다린다. 이 작법만으로도 꽤 세계가 조용해집니다.

5.7 크래시 복구를 생각하지 않는다

writer가 공유 데이터 갱신 중에 떨어지면 어떻게 하는가. 여기를 미정의인 채 본번에 내면 장해 시의 얼굴이 갑자기 심각해집니다.

Windows의 mutex는 소유 thread가 release하지 않고 끝나면 abandoned가 되고, wait 측은 WAIT_ABANDONED를 받을 수 있습니다. 이것은 공유 자원이 불확정 상태일지도 모른다는 의미입니다.12 POSIX의 robust mutex에서도 owner가 죽었을 때 EOWNERDEAD가 반환되고, 수복 후에 pthread_mutex_consistent()를 부르는 흐름이 있습니다.1314

중요한 것은 여기서 「일단 속행」하지 않는 것입니다. 복구에는 적어도 다음 중 어느 것이 필요합니다.

  • generation 번호
  • 최종 commit 완료 sequence
  • heartbeat
  • dirty / clean flag
  • journal적인 2단 commit
  • 파손 시의 전 재초기화 순서

5.8 false sharing과 캐시 라인 경합

shared memory는 빠르다, 라고 말해지기 쉽습니다. 하지만 hot한 카운터가 같은 cache line에 꽉 차 있으면 CPU 간에서 line이 왔다 갔다 해서 경기 좋게 느려집니다.

전형 예는,

  • producer가 write_index를 갱신
  • consumer가 read_index를 갱신
  • 양쪽이 같은 cache line에 타 있다

라는 녀석입니다.

이 경우는,

  • hot field를 별도 cache line으로 나눈다
  • 갱신 빈도가 높은 field와 낮은 field를 나눈다
  • 1 writer 1 cache line을 의식한다

만으로 꽤 바뀝니다. 64 bytes로 맞추는 이야기가 자주 나오지만, 64 bytes는 많은 CPU에서 흔한 값일 뿐 절대 법칙이 아니다 정도의 기분으로 봐 주세요.

5.9 이름・권한・보안을 가볍게 본다

named shared memory는 편리하지만 이름과 권한을 거칠게 하면 사고납니다.

Windows에서는,

  • Global\Local\의 namespace가 있다
  • session 0 이외에서 Global\의 file mapping을 신규 작성하려면 SeCreateGlobalPrivilege가 필요
  • object name은 event / semaphore / mutex / waitable timer / job과 namespace를 공유한다

는 버릇이 있습니다.1582

즉,

  • "Global\\MyApp"으로 했으니 service와 desktop app에서 공유할 수 있을 것 같다
  • 하지만 권한으로 실패한다
  • 게다가 동명의 mutex를 먼저 만들어 ERROR_INVALID_HANDLE이 된다

같은, 매우 Windows다운 진흙이 나옵니다.

POSIX 측에서도 shm_openmodeumask를 가볍게 보면 불필요하게 넓게 보이거나, 반대로 열 수 없거나 합니다.3

shared memory는 단지 메모리이니까 안전한 것이 아닙니다. 읽을 수 있는 권한이 있는 process에서는 꽤 자연스럽게 보입니다. 기밀 정보를 두려면 통상의 메모리와 마찬가지로 paging / swap / dump / 권한의 문맥으로 생각할 필요가 있습니다.

5.10 크기 변경과 업그레이드를 거칠게 한다

공유 메모리를 「나중에 조금 넓히고 싶다」는 꽤 위험한 요구입니다.

  • Windows의 mapping object에는 작성 시의 크기가 있다8
  • POSIX에서도 ftruncatemmap의 정합을 생각하지 않으면 참가자 측의 map 길이와 맞지 않게 됩니다316

실무에서는 크기는 그 세대에서는 불변으로 하는 편이 안전합니다. 확장이 필요하면,

  1. 새로운 version / name / generation의 segment를 만든다
  2. 참가자를 전환한다
  3. 구 segment를 닫는다

쪽이 사고율은 내려갑니다.

5.11 통지까지 전부 shared memory에 밀어넣는다

자주 있는 것이,

  • 공유 메모리에 ready = 1
  • 상대는 while (!ready) Sleep(1);

입니다.

이것, 처음에는 동작합니다. 하지만 나중에,

  • CPU를 쓸데없이 사용한다
  • Sleep(1)로 레이턴시가 흔들린다
  • 누락을 알아차리기 어렵다
  • 타임아웃이나 종료 통지를 깔끔하게 쓰기 어렵다

라는 형태로 돌아옵니다.

공유 메모리는 데이터 면에 기울이고, 통지는 기다릴 수 있는 primitive로 도망치는 편이 좋습니다.

  • Windows: event / semaphore / mutex / named pipe 등217
  • POSIX: semaphore / process-shared mutex + condvar 등1819

5.12 「이것으로 다른 머신과도 공유할 수 있다」고 생각한다

file-backed mapping을 사용해 네트워크 너머의 공유 파일을 map하면 다른 머신과도 shared memory적으로 갈 수 있지 않을까, 라고 생각하고 싶어지는 순간이 있습니다.

여기는 위험합니다.

Windows의 CreateFileMapping의 설명에서도 remote file에 대해서는 coherence가 보증되지 않는다고 되어 있습니다. 같은 페이지를 2대가 writable하게 map한 경우, 각각 자신의 쓰기밖에 보이지 않고, 디스크 갱신 시에 merge도 되지 않습니다.8

공유 메모리는 기본적으로 동일 호스트 내의 구조입니다. 머신을 걸치려면 자연스럽게 socket / RPC / message broker를 선택하는 편이 제정신을 유지하기 쉽습니다.

6. 베스트 프랙티스

6.1 control plane과 data plane을 나눈다

shared memory에는 벌크 데이터만 두고, 통지와 상태 전이는 별도 채널로 도망칩니다.

  • shared memory: frame, sample, batch, snapshot
  • event / semaphore / pipe / socket: ready, consumed, stop, error, reconnect

이 분리는 성능보다 먼저 설계의 전망을 좋게 합니다.

6.2 선두에 고정 헤더를 둔다

최저한 선두에 이런 헤더를 두는 것을 강하게 권합니다.

typedef struct SharedHeader {
    uint32_t magic;
    uint16_t abi_version;
    uint16_t header_size;

    uint32_t state;          // 0=initializing, 1=ready, 2=broken
    uint32_t flags;

    uint64_t total_size;
    uint64_t generation;
    uint64_t heartbeat_ns;

    uint64_t payload_offset;
    uint64_t payload_size;

    uint64_t write_seq;
    uint64_t read_seq;

    uint8_t  reserved[64];
} SharedHeader;

포인트는,

  • magic으로 다른 것이나 미초기화를 튕긴다
  • abi_versionheader_size로 layout 차이를 튕긴다
  • state로 초기화 도중을 튕긴다
  • generation으로 재작성을 검지한다
  • heartbeat로 사활을 본다
  • reserved로 장래 확장의 도망 길을 만든다

입니다.

shared memory에서 힘든 것은 「무엇이 일어나고 있는지 보이기 어려운」 것입니다. 그래서 관측용 metadata를 처음부터 가지게 합니다.

6.3 오프셋 참조로 한다

참조는 pointer가 아니라 offset으로 가집니다.

  • base + offset으로 해결한다
  • offset + length의 범위 체크를 넣는다
  • invalid value용의 sentinel을 결정한다

이것만으로도 address mismatch계의 사고가 꽤 줄어듭니다.

6.4 병렬 모델을 좁힌다

shared memory는 writer가 늘면 갑자기 어려워집니다. 그래서 처음에는 이 어느 쪽이 강합니다.

  • SPSC ring buffer
  • 1 writer / 다 reader의 snapshot

다 writer가 필요하면,

  • enqueue만은 lock-free / atomic
  • 실 데이터 갱신은 consumer 1개로 집약

처럼 정합성의 책임점을 줄이는 편이 대개 잘 됩니다.

6.5 commit protocol을 명시한다

「어느 순간부터 읽어도 좋은가」를 문장으로 설명할 수 없는 설계는 위험합니다.

예를 들어 더블 버퍼라면,

  1. 비공개 측 버퍼에 쓴다
  2. 체크섬이나 길이를 확정한다
  3. release 붙여 active buffer index를 전환한다
  4. reader는 acquire 붙여 active index를 읽는다
  5. 다 읽으면 index가 바뀌어 있지 않은지 확인한다

처럼 공개의 의식을 결정합니다.

6.6 크기는 세대별로 고정한다

resize in place보다,

  • name = MyShm.v3
  • abi_version = 3
  • generation = 42

처럼 세대를 자르는 편이 보수하기 쉽습니다.

공유 메모리는 API처럼 「호출 시에 형 체크」해주지 않습니다. 그래서 한번 결정한 ABI를 망가뜨리지 않는 것이 중요합니다.

6.7 관측 가능성을 넣는다

최저한 있으면 도움되는 것은 다음입니다.

  • 최종 갱신 시각
  • 최종 성공 sequence
  • drop 수 / overwrite 수
  • version mismatch 수
  • attach / detach 수
  • last error code
  • heartbeat

shared memory가 망가질 때는 대개 로그가 얇습니다. 자체 counters를 두면 장해 대응이 꽤 편해집니다.

6.8 이상계 테스트를 먼저 만든다

정상계만으로는 부족합니다. 적어도 다음은 보는 편이 좋습니다.

  • writer 갱신 중에 강제 종료
  • reader 지연으로 ring이 넘친다
  • version mismatch로 접속
  • 32bit / 64bit 혼재
  • session을 걸친 open
  • 권한 부족
  • 선행 프로세스가 오래된 세대를 가진 채 재기동
  • huge data 연속 전송 시의 cache miss / NUMA 영향

shared memory는 정상계보다 망가뜨리는 방식 테스트 쪽이 가치가 큽니다.

7. Windows와 POSIX에서 보는 포인트

관점 Windows POSIX
작성 / open CreateFileMapping / OpenFileMapping / MapViewOfFile6 shm_open / ftruncate / mmap3
디스크 비연계의 공유 INVALID_HANDLE_VALUE를 지정한 pagefile-backed mapping68 POSIX shared memory object + mmap3
초기값 pagefile-backed pages는 0 초기화8 신규 object는 길이 0. 신규 확보 바이트는 0 초기화3
동기 mutex / semaphore / event / interlocked 등25 process-shared mutex / condvar / semaphore2018
cross-process에서 사용해서는 안 되는 것 CRITICAL_SECTION, WaitOnAddress2110 PTHREAD_PROCESS_PRIVATE 그대로의 mutex / condvar2019
owner death WAIT_ABANDONED12 robust mutex + EOWNERDEAD / pthread_mutex_consistent()1314
name의 삭제 최종 handle / view 해방으로 사라진다28 shm_unlink로 이름 삭제. 참조가 남으면 실체는 마지막까지 남는다2223
namespace / 권한 Global\ / Local\, ACL, SeCreateGlobalPrivilege1524 mode, umask, 네임스페이스, O_CREAT|O_EXCL3

C#의 MemoryMappedFile도 본질적으로는 Windows의 file mapping의 래퍼입니다. 그래서,

  • 같은 이름으로 open한다
  • 별도로 mutex / event를 사용한다
  • 뷰에 대해 명시 레이아웃으로 읽는다
  • 오브젝트 참조를 그대로 두지 않는다

라는 기본은 변하지 않습니다.1

8. 먼저 보는 체크리스트

  • 정말로 공유 메모리가 필요한가. 동일 호스트에서 큰 데이터인가
  • control plane과 data plane을 나눴는가
  • 병렬 모델은 SPSC / 1 writer 다 reader까지 떨어뜨릴 수 없는가
  • 선두 헤더에 magic / version / size / state / generation / heartbeat가 있는가
  • pointer / HANDLE / fd / STL object / std::mutex를 두고 있지 않은가
  • reader가 도중 상태를 보지 않는 commit protocol이 있는가
  • 초기화자가 1명으로 정해져 있는가
  • 이상 종료 시의 복구 순서가 있는가
  • 이름과 권한을 명시하고 있는가
  • Global\이 정말 필요한가
  • resize in place를 전제로 하고 있지 않은가
  • writer kill / reader stall / version mismatch / 권한 부족을 시험했는가

9. 정리

공유 메모리는 잘 사용하면 꽤 강합니다. 특히,

  • 이미지
  • 음성
  • 센서 열
  • 큰 배치
  • 고빈도 snapshot

과 같은 동일 머신 내의 큰 데이터에서는 정말로 효과적입니다.

다만, 공유 메모리의 본체는 「빠르기」보다 책임의 이동입니다. 복사나 커널 너머의 메시징을 줄이는 대신,

  • 동기
  • 가시성
  • 초기화
  • ABI
  • 복구
  • 권한
  • 관측 가능성

을 이쪽이 떠맡게 됩니다.

그래서 최초의 1개째는 이렇게 하는 것이 안전합니다.

  • SPSC ring buffer나 더블 버퍼
  • 선두 고정 헤더
  • offset 참조
  • 별도 채널로 통지
  • version / generation / heartbeat 있음
  • 이상계 테스트 있음

이 형태부터 시작하면 shared memory는 꽤 자연스러운 도구가 됩니다. 반대로 갑자기 「무엇이든 둘 수 있는 빠른 공통 메모리」로 다루면 점차 앱이 아니라 고고학이 됩니다.

10. 참고 자료

  • Windows: file mapping과 named shared memory의 기본682
  • Windows: namespace / security / synchronization1524512
  • POSIX: shm_open, shm_unlink, mmap, process-shared / robust synchronization322162013
  • .NET: MemoryMappedFile의 개요1
  1. Microsoft Learn, “메모리 매핑 파일” https://learn.microsoft.com/en-us/dotnet/standard/io/memory-mapped-files / Microsoft Learn, “MemoryMappedFile 클래스” https://learn.microsoft.com/en-us/dotnet/api/system.io.memorymappedfiles.memorymappedfile?view=net-10.0  2 3

  2. Microsoft Learn, “Sharing Files and Memory” https://learn.microsoft.com/en-us/windows/win32/memory/sharing-files-and-memory  2 3 4 5 6 7 8

  3. man7.org, “shm_open(3)” https://man7.org/linux/man-pages/man3/shm_open.3.html  2 3 4 5 6 7 8 9 10 11

  4. Microsoft Learn, “/volatile (volatile Keyword Interpretation)” https://learn.microsoft.com/en-us/cpp/build/reference/volatile-volatile-keyword-interpretation?view=msvc-170 / Microsoft Learn, “volatile (C++)” https://learn.microsoft.com/en-us/cpp/cpp/volatile-cpp?view=msvc-170  2

  5. Microsoft Learn, “Interlocked Variable Access” https://learn.microsoft.com/en-us/windows/win32/sync/interlocked-variable-access / Microsoft Learn, “MemoryBarrier function” https://learn.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-memorybarrier  2 3 4

  6. Microsoft Learn, “Creating Named Shared Memory” https://learn.microsoft.com/en-us/windows/win32/memory/creating-named-shared-memory  2 3 4

  7. Microsoft Learn, “Scope of Allocated Memory” https://learn.microsoft.com/en-us/windows/win32/memory/scope-of-allocated-memory  2

  8. Microsoft Learn, “CreateFileMappingA function” https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createfilemappinga  2 3 4 5 6 7 8 9

  9. man7.org, “POSIX Shared Memory” training slides https://man7.org/training/download/ipc_pshm_slides-mkerrisk-man7.org.pdf 

  10. Microsoft Learn, “WaitOnAddress function” https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitonaddress  2

  11. Microsoft Learn, “MapViewOfFileEx function” https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffileex / Microsoft Learn, “MapViewOfFile function” https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile 

  12. Microsoft Learn, “Mutex Objects” https://learn.microsoft.com/en-us/windows/win32/sync/mutex-objects  2 3

  13. man7.org, “pthread_mutex_lock(3p)” https://man7.org/linux/man-pages/man3/pthread_mutex_lock.3p.html / man7.org, “pthread_mutexattr_setrobust(3)” https://man7.org/linux/man-pages/man3/pthread_mutexattr_setrobust.3.html  2 3

  14. man7.org, “pthread_mutex_consistent(3)” https://man7.org/linux/man-pages/man3/pthread_mutex_consistent.3.html / man7.org, “pthread_mutex_consistent(3p)” https://man7.org/linux/man-pages/man3/pthread_mutex_consistent.3p.html  2

  15. Microsoft Learn, “Kernel object namespaces” https://learn.microsoft.com/en-us/windows/win32/termserv/kernel-object-namespaces  2 3

  16. man7.org, “mmap(2)” https://man7.org/linux/man-pages/man2/mmap.2.html  2

  17. Microsoft Learn, “Using Mutex Objects” https://learn.microsoft.com/en-us/windows/win32/sync/using-mutex-objects 

  18. man7.org, “sem_init(3)” https://man7.org/linux/man-pages/man3/sem_init.3.html / man7.org, “sem_init(3p)” https://man7.org/linux/man-pages/man3/sem_init.3p.html  2

  19. man7.org, “pthread_condattr_setpshared(3p)” https://man7.org/linux/man-pages/man3/pthread_condattr_setpshared.3p.html / man7.org, “pthread_condattr_getpshared(3p)” https://man7.org/linux/man-pages/man3/pthread_condattr_getpshared.3p.html  2

  20. man7.org, “pthread_mutexattr_getpshared(3)” https://man7.org/linux/man-pages/man3/pthread_mutexattr_getpshared.3.html / man7.org, “pthread_mutexattr_getpshared(3p)” https://man7.org/linux/man-pages/man3/pthread_mutexattr_getpshared.3p.html  2 3

  21. Microsoft Learn, “Critical Section Objects” https://learn.microsoft.com/en-us/windows/win32/sync/critical-section-objects 

  22. Microsoft Learn, “File Mapping Security and Access Rights” https://learn.microsoft.com/en-us/windows/win32/memory/file-mapping-security-and-access-rights  2

관련 기사

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

관련 토픽

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

이 주제와 연결되는 서비스

이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.

블로그 목록으로 돌아가기