처리중입니다. 잠시만 기다려주세요.
TTJ 코딩클래스
정규반 단과 자료실 테크 뉴스 코딩 퀴즈
테크 뉴스
Hacker News 2026.07.08 32

비대칭 펜스: 자주 도는 코드는 공짜로, 비용은 한쪽에 몰아주는 동시성 최적화

Hacker News 원문 보기
비대칭 펜스: 자주 도는 코드는 공짜로, 비용은 한쪽에 몰아주는 동시성 최적화

'자주 도는 코드'를 공짜로 만드는 마법 같은 기법

멀티스레드(여러 작업 흐름을 동시에 돌리는 것) 프로그래밍을 하다 보면 꼭 만나는 골칫거리가 있어요. 바로 메모리 순서(memory ordering) 문제예요. 이게 뭐냐면, 우리가 코드에 'A를 먼저 하고 B를 해라'라고 순서대로 적어도 CPU와 컴파일러가 속도를 높이려고 그 순서를 제멋대로 바꿔서 실행할 수 있다는 거예요. 혼자 도는 프로그램에서는 최종 결과만 맞으면 되니 문제가 없는데, 여러 스레드가 같은 메모리를 동시에 건드리면 이 순서 뒤바뀜 때문에 아주 미묘한 버그가 터지거든요.

그래서 쓰는 게 메모리 펜스(memory fence, 메모리 배리어라고도 해요)예요. '여기까지의 작업은 반드시 끝내고 다음으로 넘어가라'라고 CPU에게 못을 박는 명령이죠. 문제는 이 펜스가 엄청 비싸다는 거예요. CPU가 쌓아둔 쓰기 작업을 전부 비우고, 다른 코어와 상태를 맞추느라 수십에서 수백 사이클을 잡아먹거든요. 그래서 아주 자주 실행되는 코드(핫 패스)에 펜스가 하나만 끼어 있어도 전체 성능이 뚝 떨어져요.

문제는 '대칭'이라는 데 있어요

보통 두 스레드가 데이터를 주고받으며 동기화하려면 양쪽 다 펜스를 걸어야 해요. 서로 신호를 맞춰야 하니까요. 그런데 현실의 많은 상황은 사실 비대칭이에요. 예를 들어 어떤 데이터를 '읽기'만 하는 스레드는 1초에 수백만 번 도는데, 그 데이터를 '바꾸는' 스레드는 어쩌다 한 번만 돌거든요. 이런 상황에서 자주 도는 읽기 쪽에까지 비싼 펜스를 매번 거는 건 너무 아깝잖아요.

비대칭 펜스(asymmetric fence)의 아이디어가 바로 여기서 나와요. '자주 도는 쪽(가벼운 펜스)'에서는 비용을 거의 0으로 만들고, '어쩌다 도는 쪽(무거운 펜스)'에 모든 비용을 몰아주자는 거죠. 자주 도는 쪽에는 CPU 명령을 아예 넣지 않고 컴파일러가 순서를 못 바꾸게만 막아둬요. C++에서는 std::atomic_signal_fence가 딱 이 역할을 하는데, 실제 CPU 명령은 하나도 만들어내지 않는 '컴파일러 전용 배리어'거든요. 대신 어쩌다 도는 쪽에서 강력한 한 방을 날려 다른 모든 스레드까지 강제로 동기화시키는 거예요.

그 '강력한 한 방'이 바로 membarrier

이 마법을 가능하게 해주는 게 리눅스의 membarrier() 시스템 콜이에요. 보통 펜스는 그 명령을 실행한 스레드 하나에만 효과가 있는데, membarrier()는 특별해요. 한 스레드가 이걸 호출하면 같은 프로세스에 속한 다른 모든 스레드가 마치 그 순간 펜스를 실행한 것처럼 강제로 만들어줘요. 커널이 각 CPU에 인터럽트를 보내거나 문맥 교환(스레드가 CPU를 넘겨받는 지점)을 이용해서 실제로 그 효과를 뿌려주는 거죠.

그러니 전체 그림은 이렇게 돼요. 자주 도는 읽기 스레드들은 공짜나 다름없는 컴파일러 배리어만 갖고 신나게 달리고, 어쩌다 데이터를 바꾸는 스레드가 membarrier()를 한 번 불러서 '자, 다들 여기서 순서 맞춰!'라고 전체에 명령을 내리는 거예요. 비싼 비용을 1초에 수백만 번이 아니라 어쩌다 한 번만 내면 되니 전체 성능이 극적으로 좋아지죠. 다만 이 글이 짚는 까다로운 지점은, C++의 공식 메모리 모델이 membarrier 같은 걸 상정하고 만들어진 게 아니라서 이 기법이 '진짜로 항상 옳은가'를 엄밀하게 증명하기가 은근히 어렵다는 점이에요. 가벼운 쪽에 최소한의 컴파일러 배리어는 왜 꼭 필요한지, 그게 빠지면 어떤 재배치가 몰래 일어나는지 같은 디테일을 파고들죠.

업계에서는 이미 쓰고 있어요

이 기법은 학술적 호기심이 아니라 실전 무기예요. 메타(페이스북)의 C++ 라이브러리 Folly에는 asymmetric_thread_fence가 이미 들어 있고, 자주 읽고 가끔 쓰는 자료구조인 RCU(Read-Copy-Update)해저드 포인터(hazard pointer) 같은 고성능 동시성 기법의 핵심 재료로 쓰여요. 자바의 예전 편향 락(biased locking) 구현도 비슷한 아이디어를 활용했고요. 읽기가 압도적으로 많은 상황이라면 어디든 응용할 수 있는 패턴이죠.

한국 개발자에게

사실 대부분의 서비스 개발에서는 여기까지 직접 내려갈 일이 많지는 않아요. 하지만 고성능 서버, 데이터베이스 엔진, 게임 서버처럼 락 경합이 성능을 좌우하는 영역을 다룬다면 이 개념은 알아둘 가치가 충분해요. 당장 코드에 쓰지 않더라도 '동기화 비용을 한쪽으로 몰아줄 수 있다'는 사고방식 자체가 성능 튜닝의 시야를 넓혀주거든요. 무엇보다 우리가 매일 쓰는 std::atomic이나 memory_order 뒤에서 CPU가 실제로 무슨 일을 하는지 이해하는 좋은 계기가 돼요.

한줄 정리: 자주 도는 코드의 펜스 비용을 0으로 만들고 그 비용을 어쩌다 도는 쪽에 몰아주는 게 비대칭 펜스이고, 리눅스 membarrier()가 이를 현실로 만들어줘요.

여러분은 멀티스레드 코드에서 메모리 순서 때문에 골머리를 앓아본 적 있으신가요? 이런 저수준 최적화, 실무에서 실제로 마주친 경험이 있다면 들려주세요.


🔗 출처: Hacker News

이 뉴스가 유용했나요?

TTJ 코딩클래스 정규반

월급 외 수입,
코딩으로 만들 수 있습니다

17가지 수익 모델을 직접 실습하고, 1,300만원 상당의 자동화 도구와 소스코드를 받아가세요.

144+실전 강의
17개수익 모델
4.9수강생 평점
정규반 자세히 보기

"비전공 직장인인데 반년 만에 수익 파이프라인을 여러 개 만들었습니다"

실제 수강생 후기
  • 비전공자도 6개월이면 첫 수익
  • 20년 경력 개발자 직강
  • 자동화 프로그램 + 소스코드 제공

매일 AI·개발 뉴스를 받아보세요

주요 테크 뉴스를 매일 아침 이메일로 전해드립니다.

스팸 없이, 언제든 구독 취소 가능합니다.