volatile 키워드는 컴파일러에게 변수가 컴파일러의 예상과 다르게 변경될 수 있음을 알려주며(예: 하드웨어, 다른 스레드 또는 인터럽트 핸들러에 의해), 그 값을 캐시하거나 접근을 최적화하지 않도록 합니다.
사용되는 경우:
volatile은 원자성이나 동기화를 보장하지 않습니다!).volatile int flag = 0; void handler() { flag = 1; // 인터럽트 핸들러 } void loop() { while (!flag) { // 이벤트 대기 } // ... }
volatile 없이 컴파일러는 루프를 무한 루프로 대체할 수 있었지만, volatile을 사용하면 변수는 매번 메모리에서 읽혀집니다.
스레드 간의 정보 교환을 올바르게 수행하기 위해
volatile을 사용하는 것만으로 충분합니까?
자주 발생하는 오류는 volatile이 스레드 간의 메모리 동기화를 보장하고 원자성을 부여한다고 생각하는 것입니다.
정답:
volatile은 멀티스레딩 환경에서 데이터 경쟁으로부터 보호하지 않으며 메모리 장벽을 보장하지 않습니다: 이는 컴파일러에게 접근을 최적화하지 말라고 지시할 뿐입니다. 보장된 정확성을 위해 반드시 동기화 원시(예: mutex, atomic 작업 등)를 사용해야 합니다.
// 안전하지 않습니다! volatile int ready = 0; void thread1() { data = 123; // 데이터 기록 ready = 1; // 다른 스레드에 신호 } void thread2() { while (!ready); // 이벤트 대기 printf("data = %d\n", data); // 데이터가 아직 업데이트되지 않았을 수 있습니다! }
역사
volatile로 정의되지 않은 경우 펌웨어가 때때로 멈췄습니다 — 플래그 변경이 감지되지 않았습니다.volatile을 사용하여 충분하다고 생각했지만, 결국 어려운 버그를 발견하게 되었습니다: 때때로 스레드가 관련 없는 데이터를 읽거나 완전히 불일치된 상태로 읽었습니다 — 원자 변수나 mutex가 필요했습니다.volatile 없이 쓰거나 읽으며, 컴파일러가 접근을 최적화함에 따라 레지스터의 새로운 값을 완전히 무시하게 되었고 — 일부 명령이 작동하지 않았습니다.