멀티 쓰레드 프로그램에서 공유 변수는 항상 volatile 로 해야 하나요?

freezm7의 이미지

멀티 쓰레드 프로그램에서,

int i;

를 a, b 쓰레드에서 공유할 때,

i 를 volatile int i; 로 선언하지 않으면

쓰레드간에 서로 다른 값을 참조하는 경우가 생길수 있나요?

CPU 캐시가 한 프로세스 내에서는 쓰레드가 달라도 같은 값으로 보이는게 아닌가 싶은데... 멀티 코어에서는 또 문제가 될 것도 같고...

이에 관해서 정확히 아시는 분 계신가요?

jick의 이미지

요즘 컴퓨터에서는 volatile int i로 선언해도 쓰레드 간에 서로 다른 값을 참조하는 경우가 생길 수 있습니다.

예를 들어서 다음과 같은 코드에서,

volatile int i = 0, j = 0;
 
thread A:
i = 1;
j = 2;
 
thread B:
if (j == 2) {
  if (i == 0)
    printf("Impossible!");
  ......
}

상식적으로 생각하면 i를 1로 바꾼 다음 j를 2로 바꿨으니까 만약 j == 2를 읽고 i를 읽으면 i가 무조건 1이어야 할 것 같지만 요즘 CPU는 명령어 순서를 바꿔 수행할 수 있기 때문에 i가 0이 될 수 있습니다.

이런 문제를 해결하려면 memory barrier나 critical section을 써야 하는데 성능이 엄청 중요하지 않은 한 그냥 critical section을 쓰는 게 속편합니다. (그리고 critical section을 쓸 때에는 volatile이 별 의미가 없습니다.)

다만 volatile을 써서 효과를 보는 것은, 컴파일러가 최적화를 안하기 때문에 계~속 읽다 보면 언젠가는 아까 쓴 값을 읽기는 합니다. 즉 한쪽에서 i = 1이라고 써주고 아무도 다른 값을 쓰지 않고 다른 쓰레드에서 i를 계속 읽고 있다 보면 언젠가는 그 값이 1이 됩니다.

freezm7의 이미지

"요즘 CPU는 명령어 순서를 바꿔 수행할 수 있기 때문에"

명령어 순서를 바꾸는건 컴파일러가 최적화를 할때 하던 것처럼 한다는 말인가요?

CPU 가 그것을 한다니.. 놀랍군요..

근데 문제의 소지도 많아 보이네요.

예전에 잘 돌아가던 프로그램이 새 CPU 에서는 제대로 돌지 않을 수도 있단 건데,,,

CPU 가 하위 호환성을 지원하지 못한다니....

근데 저렇게 CPU 가 명령 순서를 바꾸는 기술 이름이 뭔가요? 이름을 알아야, 왜 저렇게 바꾸는지 찾아볼텐데..

즐겁게 살아 볼까나~*

jick의 이미지

http://en.wikipedia.org/wiki/Out-of-order_execution

대충 훑어봤는데 그다지 잘 설명이 되었는지는 모르겠네요. 더 자세히 알고 싶으시면 좀 두께가 빡세지만 다음 책에 자세히 나와 있습니다. 피가 되고 살이 되는 책이니 한번에 다 보겠다고 욕심내지 마시고, 책장의 품격을 높인다고-_- 생각하시고 사서 꽂아두고 틈틈이 보시는 것도 괜찮을 듯...

Computer Architecture: A Quantitative Approach
John L. Hennessy and David A. Patterson

(저는 2판으로 공부했는데 지금 찾아보니 4판이 최신이군요.)

freezm7의 이미지

컴퓨터 구조 라는 수업에서 교재로 했던 책입니다.

파이프라인에 대해서 자세히 나와 있죠.

근데 명령어 순서를 바꾼다는 것까진 제가 본 내용중엔 없어서 의문이네요..

즐겁게 살아 볼까나~*

jick의 이미지

흠 없었나요? 그럴리가... (갸우뚱)

파이프라인 관련 부분을 다시 읽어보시면 거기서 설명하는 대로 하면 메모리 입출력 순서가 뒤죽박죽이 되겠구나 하고 이해가 가실 겁니다. 각 기계어 명령이 공식적으로 완료되는(retire) 시점은 순서대로지만, CPU 안에서의 수행 순서는 멋대로죠.

음, 다시 생각해 보니 멀티쓰레드 관련해서는 이 논문을 읽어보시는 게 가장 좋을 것 같습니다. (좀 옛날 거긴 합니다. 요즘에 더 좋은 논문이 나왔는지는 모르겠네요, 최근 몇 년간 이쪽을 안 들여다봐서...)

Shared Memory Consistency Models: A Tutorial (1995)
Sarita V. Adve, Kourosh Gharachorloo

http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.53.4665

tj의 이미지

다음 책도 괜찮습니다.

http://www.amazon.com/UNIX-Systems-Modern-Architectures-Multiprocessing/dp/0201633388

그리고 커널 소스안에 Documentation/memory-barriers.txt 도 정리가 잘 돼있는 편입니다. 자세하게는 아키텍쳐 메뉴얼마다 메모리 모델 설명하는 부분이 있습니다.

pastime의 이미지

3판 기준으로 3.2절부터 설명이 나옵니다.

3.2 Overcoming Data Hazards with Dynamic Scheduling

freezm7의 이미지

제가 본게 2판이었거든요.

그래서 없었나 보네요 ㅠ.ㅠ

상당히 최근에야 널리 쓰이게 된 기술인가 보네요..

즐겁게 살아 볼까나~*

pastime의 이미지

dynamic scheduling을 설명하는 Tomasulo의 알고리즘은 이미 1967년에 발표된 것입니다.
제가 2판을 보지는 않았지만
ILP의 핵심인 이 내용이 빠져있지는 않을 것으로 생각됩니다.

혹시 "Computer Architecture"와 "Computer Orgarnization and Design"을
혼동하신 것은 아닐까요?

mg2000의 이미지

명령어 순서를 바꾸는 것을 Out-of-order Execution이라고 하는데요.

http://en.wikipedia.org/wiki/Out-of-order_execution

위키피디아를 보니 옛날부터 쓰인 기술이네요.

인텔 세미나 가보니 이게 전력 소모가 많아서, 모바일 CPU에는 이게 안들어간다고 하더군요.

(모바일 CPU가 느린 이유가 요게 한 몫 할듯)

mac040의 이미지

수퍼 스칼라와 파이프 라인을 고려하면 CPU 내부에서의 명령 실행 순서는 바뀔 수 있습니다만 CPU 외부에서 봤을때는 순차적으로 실행되는 것으로 알고 있습니다. 물론 컴파일러가 실행 순서를 바꿀 경우는 있지만 Volatile 선언시, 이는 결과에 영향을 미치지 않는 범위로 알고 있습니다.

다른 문제로(추정) 멀티 쓰레드간의 공유 변수 변경에 크리티컬 색션을 선언하지 않을 경우 일어날 수 있는 문제로 특정 변수 변경이 단일 명령어로 실행되지 않을 수 있는 점이 보이네요. 즉 C에서는 한개의 명령어 처럼 보이지만(사실 코드 상의 하나의 라인이죠) 실제 기계어로는 여러개의 명령어이며, 쓰레드 스위칭을 라인 실행 여부에 관계 없이 이루어지죠.

특히 서로 해당 값을 읽고 바꾸는 경우, 문제가 일어날 확률이 다분히 높고요. 특별한 경우, (예를 들어 CPU에서 64비트 쓰기를 지원하지 않는데 64비트 변수를 써서 32비트씩 2회에 나뉘어 쓰이는데, 그 중간에 스위칭이 일어나던가 하는 상황) 말도 안되는 이상한 값을 읽어 올 수도 있습니다.

뭐 그냥 크리티컬로 잠그는게 좋지 않을 까요?

jick의 이미지

"CPU 내부에서의 실행 순서는 바뀔 수 있지만 외부에서 봐서는 순차적으로 실행"되는 게 아니라, 반대로

"CPU 외부에서 본 실행 순서는 바뀔 수 있지만 내부의 프로그램은 순차적으로 실행된다고 생각"하는 것입니다.

이를테면 주소 100에 1을 쓰고 주소 200의 값을 읽는 명령이 순차적으로 나올 때, CPU가 보기에 이 둘은 주소가 다르므로 주소 200의 값을 메모리에서 먼저 읽은 다음 100에다 1을 써도 프로그램 입장에서는 구분할 도리가 없습니다.

이게 실질적으로 프로그램 동작에 차이를 발생시키는 경우는, 시스템 안에 여러 개의 CPU(혹은 core)가 있을 경우입니다. 옆 CPU에서 수행되는 프로그램이 볼 때 차이가 생길 수 있죠.

ktd2004의 이미지

다음을 참고하시면 좋을 것 같습니다.

http://skyul.tistory.com/337

magingax의 이미지

별로 안전하지 않습니다.
공유변수는 무조껀 cs 로 락걸어야됩니다.

LISP 사용자모임
http://cafe.naver.com/lisper

LISP 사용자모임
http://cafe.naver.com/lisper
방송기술 개발업체
http://playhouseinc.co.kr

ohhara의 이미지


volatile을 써 주면 compiler가 해당 variable을 접근하는 부분의 instruction의 순서를 보장해 줍니다.

int a;
volatile int b;
int c;

a = 1;
b = 2;
c = 3;

이 있을 때 instruction의 순서상으로 b에 2를 넣는 instruction은 a에 1을 넣은 instruction 뒤에 오고 c에 3을 넣는 instruction전에 옵니다.

volatile이 생략되면 instruction간에 dependency가 없다고 compiler가 판단했을 경우 순서는 마음대로 바뀔 수 있습니다.

하지만 volatile은 instruction의 순서만 보장할 뿐(표준이 아니니 보장을 하지 못할 수도?) 실제로 cpu에서 실행이 순서대로 일어날 것을 보장하지는 못합니다. 즉, cpu가 instruction을 해석해 봤을 때 instruction간에 dependency가 없다고 판단되면 cpu가 instruction의 순서를 마음대로 바꿔서 실행합니다. 이런 동작은 single cpu, single task로 작동할 때는 문제가 되지 않지만 multi task로 작동하거나 multi core에서 작동하거나 하게 되면 여러가지 문제가 발생하게 됩니다.(data synchronization, data consistency등)

그래서 이런 경우를 해결하기 위해 cpu에 보면 보통 memory barrier관련 instruction이 있습니다. 예를 들어 write barrier instruction은 write barrier instruction의 이전 instruction의 write를 모두 수행한 뒤에 해당 write barrier instruction 이후의 write를 수행하도록 보장해 줍니다. 이 instruction을 wb()라고 가상적으로 정의한다면 다음과 같이 사용이 가능합니다.

int a;
volatile int b;
int c;

a = 1;
wb();
b = 2;
wb();
c = 3;

이와같이 하면 a에 1을 write하는 instruction이 수행된 뒤에 b에 2를 write하는 instruction이 수행되는 것이 보장됩니다. 그리고 b에 2를 write하는 instruction이 수행된 뒤에 c에 3을 write하는 instruction이 수행되는 것이 보장됩니다.

참고로 memory barrier는 여러가지 종류가 있으며 용도에 맞춰서 잘 선택해서 사용해야 됩니다. write barrier뿐만이 아니라 read barrier도 있으며, 단순해 보이는 write barrier도 살펴보면 cpu자신 안쪽에서만 순서를 보장해 주는 것도 있고 write pipeline을 flush해서 cpu바깥쪽에서까지 순서를 보장해 주는 것도 있습니다. 필요에 따라서는 memory barrier를 사용하면서 cpu cache를 flush하거나 invalidate해야 할 필요가 있기도 합니다.

firmware, os, device driver의 개발자가 아니시라면 그냥 아무생각없이 volatile과 lock을 함께 사용하는 것을 추천합니다. ^^;;;

Taeho Oh ( ohhara@postech.edu , ohhara@plus.or.kr ) http://ohhara.sarang.net
Postech ( Pohang University of Science and Technology ) http://www.postech.edu
Digital Media Professionals Inc. http://www.dmprof.com

Taeho Oh ( ohhara@postech.edu ) http://ohhara.sarang.net
Postech ( Pohang University of Science and Technology ) http://www.postech.edu
Alticast Corp. http://www.alticast.com

tj의 이미지

locking만 제대로 하면 volatile 사용할 필요는 없습니다. 잘못된 코드를 쓰면서 volatile이니까 잘 될거야 -_-;; 라고 위약 효과를 경험하는 경우가 많고, 실제로 필요한 경우에도 변수를 volatile로 선언해버리면 다른 부분에 묻혀서 나중에 volatile을 왜 썼는지 알기가 힘들어지기때문에 꼭 필요한 부분에만 volatile *로 캐스팅해서 쓰는 편이 좋습니다. 그런데 libc에 베리어들이 정의되어있지 않기 때문에 유저스페이스에서 이걸 맞게 쓸 수 있는 경우가 사실상 없다시피 합니다. IO영역 mmap해서 쓰는 경우를 제외하곤 그냥 정직하게 lock/condvar 쓰길 추천드립니다.

pastime의 이미지

volatile은 단지 load/store instruction이 만들어진다는 것만 보장하지 않나요?

instruction이 생성되는 순서를 보장하려면 compiler 단의 memory barrier를 이용해야 하는 것으로 알고있습니다만..

eungkyu의 이미지

쓰신 내용은 일반적인 memory barrier에 대한 내용으로 보입니다.

volatile의 경우는

volatile int a;

a = 1;
a = 2;
a = 3;

이렇게 했을 때 a = 1, a = 2이걸 최적화해서 생략하지 말고
a에 1, 2, 3을 차례 차례 진짜로 쓰라는 의미이지,
여러 변수 사이의 order를 정해주는 의미는 아닙니다.

예를 들어

volatile int a;
int b;
int c;

a = 1;
b = 2;
c = 3;

여기서 a, b, c가 뭐가 먼저 assign될지 여부와 volatile은 관계가 없어보입니다.
여기서 volatile의 역할은 a가 나중에 쓰이지 않더라도 a에는 반드시 1을 assign하게
하는 것입니다.

tj의 이미지

volatile 사이의 순서는 정해줍니다. 컴파일러가 volatile끼리 reorder 시켜버리면 IO영역 mmap해서 사용할 수가 없으니까요.

ohhara의 이미지

오해의 소지가 있어서 보충설명 추가합니다.

compiler에 따라 volatile에 memory barrier의 기능이 있기도 하고 없기도 합니다.
아마도 없는 것이 표준이고 있는 것을 compiler의 확장기능으로 보는 것이 좋을 듯 합니다.

이 글의 답글단 분들의 설명이 더 정확합니다.

Taeho Oh ( ohhara@postech.edu , ohhara@plus.or.kr ) http://ohhara.sarang.net
Postech ( Pohang University of Science and Technology ) http://www.postech.edu
Digital Media Professionals Inc. http://www.dmprof.com

Taeho Oh ( ohhara@postech.edu ) http://ohhara.sarang.net
Postech ( Pohang University of Science and Technology ) http://www.postech.edu
Alticast Corp. http://www.alticast.com

eungkyu의 이미지

전 volatile에서 instruction 순서, lock 등에 대해선 전혀 들은 바가 없는데
그에 대해 주장하는 사람이 많아서 잠시 찾아봤습니다.

보니까 Java에서 사용하는 volatile 키워드에 그런 의미가 있었군요.

http://en.wikipedia.org/wiki/Volatile_variable

질문하신 분의 context가 C/C++, Java가 모두 될 수 있어서 더욱 혼란이 가중되는 듯 하네요.

C/C++과 Java의 volatile은 의미가 전혀 다르니 구분하는 것이 필요할 듯 합니다.

freezm7의 이미지

C/C++ 기준으로 질문을 올린 것이었습니다.

(사실 JAVA 에도 있는줄 모르고 있었습니다.)

즐겁게 살아 볼까나~*

eungkyu의 이미지

C/C++에서 volatile과 멀티쓰레드는 별 관계가 없는 것으로 알고 있습니다.

wikipedia에서 나온 것처럼 빌드하는 프로그램이 아닌 제3의 곳에서
해당 변수의 값을 참조하거나, 해당 변수의 값에 영향을 받을 수 있을 경우에 대비하여
그 변수에 대한 최적화를 하지 말라는 의미입니다.

예는 wikipedia에 잘 설명되어 있네요.

charsyam의 이미지

기본적으로

C/C++ volite memory barrior 기능 없음

Java/.NET volite memory barroir 기능 있음 으로 보시는게 좋을듯 하네요.

그냥 Lock 이 장땡이라는 쿨럭...

=========================
CharSyam ^^ --- 고운 하루
=========================

=========================
CharSyam ^^ --- 고운 하루
=========================

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.