"휘발성"키워드는 무엇입니까?


130

volatile키워드 에 대한 기사를 읽었 지만 올바른 사용법을 알 수 없습니다. C #과 Java에서 무엇을 사용해야하는지 말씀해 주시겠습니까?


1
휘발성의 문제점 중 하나는 둘 이상의 것을 의미한다는 것입니다. 펑키 최적화를하지 않는 것이 컴파일러에게 정보가되는 것은 C의 유산입니다. 그것은 또한 메모리 장벽 액세스에 사용되어야한다는 것을 의미한다. 그러나 대부분의 경우 성능이 저하되고 사람들을 혼란스럽게합니다. : P
AnorZaken

답변:


93

C #과 Java 모두 "휘발성"은 변수 자체의 값이 프로그램 자체의 범위 밖에서 변경 될 수 있으므로 변수의 값을 캐시해서는 안된다는 것을 컴파일러에 알려줍니다. 그런 다음 컴파일러는 변수가 "제어 범위를 벗어나"변경되면 문제를 일으킬 수있는 최적화를 피합니다.


@Tom-정식으로 언급하고 수정했습니다.
Will A

11
여전히 그보다 훨씬 미묘합니다.
Tom Hawtin-tackline

1
잘못된. 캐싱을 방지하지 않습니다. 내 대답을 참조하십시오.
doug65536

168

이 예제를 고려하십시오.

int i = 5;
System.out.println(i);

컴파일러는 다음과 같이 5를 인쇄하도록 최적화 할 수 있습니다.

System.out.println(5);

그러나 변경할 수있는 다른 스레드 i가있는 경우 잘못된 동작입니다. 다른 스레드 i가 6으로 변경 되면 최적화 된 버전은 여전히 ​​5를 인쇄합니다.

volatile키워드는 최적화 및 캐싱을 방지하며, 따라서 변수가 다른 스레드에 의해 변경 될 수있는 경우 유용하다.


3
나는 여전히로 i표시된 최적화가 유효하다고 생각합니다 volatile. Java에서는 모든 것이 이전발생한 관계에 관한 것입니다.
Tom Hawtin-tackline

게시 주셔서 감사합니다, 그래서 어떻게 휘발성 가변 잠금 연결이 있습니까?
Mircea

@Mircea : 그것이 휘발성으로 표시하는 것이 전부라고 들었습니다. 필드를 휘발성으로 표시하면 스레드가 주어진 변수에 대해 일관된 값을 볼 수 있도록 내부 메커니즘을 사용하지만 위의 답변에는 언급되지 않았습니다 ... 어쩌면 누군가가 이것을 확인할 수 있습니까? 감사합니다
npinti

5
@ Sjoerd :이 예제를 이해하지 못했습니다. 경우 i지역 변수이고, 다른 쓰레드는 어쨌든 그것을 변경할 수 있습니다. 필드 인 경우 컴파일러가이 아닌 경우 호출을 최적화 할 수 없습니다 final. 필자는 필드가 final명시 적으로 선언되지 않은 경우 필드가 "보인다"는 가정에 따라 컴파일러가 최적화를 할 수 있다고 생각하지 않습니다 .
polygenelubricants

1
C #과 java는 C ++이 아닙니다. 이것은 정확하지 않습니다. 캐싱을 방해하지 않으며 최적화를 방해하지 않습니다. 약한 순서의 메모리 아키텍처에 필요한 읽기-취득 및 저장소 릴리스 의미에 관한 것입니다. 투기 적 실행에 관한 것입니다.
doug65536

40

휘발성이 변수에 어떤 영향을 미치는지 이해하려면 변수가 휘발성이 아닌 경우 어떻게되는지 이해하는 것이 중요합니다.

  • 변수는 비 휘발성

두 개의 스레드 A & B가 비 휘발성 변수에 액세스 할 때 각 스레드는 변수의 로컬 사본을 로컬 캐시에 유지합니다. 로컬 캐시에서 스레드 A가 수행 한 변경 내용은 스레드 B에 표시되지 않습니다.

  • 변수는 휘발성

변수가 휘발성으로 선언되면 이는 스레드가 그러한 변수를 캐시해서는 안된다는 의미입니다. 즉, 스레드는 주 메모리에서 직접 읽지 않는 한 이러한 변수의 값을 신뢰해서는 안됩니다.

그렇다면 언제 변수를 휘발성으로 만들어야합니까?

많은 스레드가 액세스 할 수있는 변수가 있고 값이 프로그램의 다른 스레드 / 프로세스 / 외부에서 업데이트 된 경우에도 모든 스레드가 해당 변수의 최신 업데이트 값을 얻으려고 할 때.


2
잘못된. "캐싱 방지"와는 아무런 관련이 없습니다. 컴파일러에 의해 또는 추론 적 실행을 통해 CPU 하드웨어를 재정렬하는 것입니다.
doug65536

37

휘발성 필드의 읽기는 의미를 얻습니다 . 이는 휘발성 변수에서 읽은 메모리가 다음 메모리를 읽기 전에 발생한다는 것을 보장합니다. 컴파일러가 재정렬을 수행하지 못하도록 차단하고 하드웨어에 필요한 경우 (약한 순서의 CPU), 특수 명령을 사용하여 휘발성 읽기 이후에 발생하지만 예측 적으로 일찍 시작되거나 CPU가 읽을 수있는 모든 읽기를 하드웨어가 플러시하도록합니다. 부하 획득 문제와 폐기 시점 사이에 투기 적 부하가 발생하지 않도록하여 초기에 조기에 발행되는 것을 방지합니다.

휘발성 필드의 쓰기에는 릴리스 시맨틱이 있습니다. 이는 모든 이전 메모리 쓰기가 다른 프로세서에 표시 될 때까지 휘발성 변수에 대한 모든 메모리 쓰기가 지연되도록 보장합니다.

다음 예제를 고려하십시오.

something.foo = new Thing();

경우 foo클래스의 멤버 변수이고, 다른 CPU에 의해 언급 된 개체 인스턴스에 액세스 할 수 있습니다 something, 그들은 값이 표시 될 수 있습니다 foo변화를 하기 전에 의 메모리 쓰기 Thing생성자는 전 세계적으로 볼 수 있습니다! 이것이 "약하게 정렬 된 메모리"라는 의미입니다. 컴파일러가에 저장하기 전에 생성자에 모든 저장이있는 경우에도 발생할 수 있습니다 foo. 경우 foo입니다 volatile후하는 가게 foo릴리스 의미를 가질 것이며, 하드웨어 보증은 쓰기 전에 쓰기의 모든 것을에 foo쓰기가 허용하기 전에 다른 프로세서에 볼 수 있습니다 foo발생할 수 있습니다.

쓰기 foo순서가 너무 나빠질 수 있는 방법은 무엇입니까? 캐시 라인 보유 foo가 캐시에 있고 생성자의 저장소가 캐시를 놓친 경우 캐시 누락에 대한 쓰기보다 훨씬 빨리 저장소를 완료 할 수 있습니다.

인텔의 (끔찍한) Itanium 아키텍처는 메모리를 약하게 주문했습니다. 원래 XBox 360에 사용 된 프로세서의 메모리가 약합니다. 널리 사용되는 ARMv7-A를 포함한 많은 ARM 프로세서는 메모리를 약하게 주문했습니다.

잠금과 같은 것들이 본질적으로 동시에 의미를 획득하고 릴리스하는 것과 동일한 전체 메모리 장벽을 수행하기 때문에 개발자들은 종종 이러한 데이터 경쟁을 보지 못합니다. 잠금을 획득하기 전에 잠금 내부의로드를 추론 적으로 실행할 수 없으며 잠금을 획득 할 때까지 지연됩니다. 잠금 해제에서 저장을 지연시킬 수 없으며 잠금 해제 명령은 잠금 내부에서 수행 된 모든 쓰기가 전체적으로 표시 될 때까지 지연됩니다.

보다 완전한 예는 "이중 체크 잠금"패턴입니다. 이 패턴의 목적은 객체를 지연 초기화하기 위해 항상 잠금을 획득하지 않아도되는 것입니다.

Wikipedia에서 n

public class MySingleton {
    private static object myLock = new object();
    private static volatile MySingleton mySingleton = null;

    private MySingleton() {
    }

    public static MySingleton GetInstance() {
        if (mySingleton == null) { // 1st check
            lock (myLock) {
                if (mySingleton == null) { // 2nd (double) check
                    mySingleton = new MySingleton();
                    // Write-release semantics are implicitly handled by marking
                    // mySingleton with 'volatile', which inserts the necessary memory
                    // barriers between the constructor call and the write to mySingleton.
                    // The barriers created by the lock are not sufficient because
                    // the object is made visible before the lock is released.
                }
            }
        }
        // The barriers created by the lock are not sufficient because not all threads
        // will acquire the lock. A fence for read-acquire semantics is needed between
        // the test of mySingleton (above) and the use of its contents. This fence
        // is automatically inserted because mySingleton is marked as 'volatile'.
        return mySingleton;
    }
}

이 예에서 MySingleton생성자에있는 상점은에 저장하기 전에 다른 프로세서에 표시되지 않을 수 있습니다 mySingleton. 이런 일이 발생하면 mySingleton을 들여다 보는 다른 스레드는 잠금을 얻지 못하며 반드시 생성자에 대한 쓰기를 수행하지는 않습니다.

volatile캐싱을 방지하지 않습니다. 이 기능은 다른 프로세서가 "참조"하는 순서를 보장합니다. 상점 릴리스는 보류중인 모든 쓰기가 완료되고 관련 회선이 캐시 된 경우 다른 프로세서가 캐시 회선을 버리고 / 기록하도록 지시하는 버스주기가 발행 될 때까지 저장소를 지연시킵니다. 로드 획득은 추측 된 읽기를 플러시하여 과거의 오래된 값이되지 않도록합니다.


좋은 설명입니다. 좋은 이중 점검 잠금 예도 있습니다. 그러나 캐싱 측면에 대해 걱정할 때 언제 사용 해야하는지 확실하지 않습니다. 스레드가 1 개만 작성되고 스레드가 1 개만 읽는 대기열 구현을 작성하는 경우 잠금없이 얻을 수 있고 머리와 꼬리 "포인터"를 일시적으로 표시 할 수 있습니까? 독자와 작가 모두 최신 값을 볼 수 있도록하고 싶습니다.
nickdu

모두 headtail필요가 가정에서 생산을 방지하기 위해 휘발성이 될 tail하지 않습니다 변화를, 그리고 가정에서 소비자를 방지하기 위해 head변경되지 않습니다. 또한 head저장소가 전체적으로 표시되기 전에 큐 데이터 쓰기가 전체적으로 표시되도록 휘발성이어야합니다 head.
doug65536

+1, 불행히도 최신 / "최신 업데이트"와 같은 용어는 단일 값의 개념을 암시합니다. 실제로 두 경쟁자는에 결승선을 통과 할 수 정확히 같은 시간 - CPU에서 두 개의 코어가에서 쓰기를 요청할 수 있습니다 정확히 같은 시간 . 결국 코어는 교대로 작업을 수행하지 않으므로 멀티 코어가 무의미합니다. 좋은 멀티 스레드 사고 / 디자인은 저수준 "최신"을 강요하는 데 중점을 두지 않아야합니다. 잠금은 코어가 공정하지 않고 한 번에 한 명의 스피커 를 임의로 선택 하도록 강요하기 때문에 본질적으로 가짜입니다 . 부 자연스러운 개념이 필요합니다.
AnorZaken

34

휘발성 키워드는 모두 자바와 C #에서 다른 의미를 가지고있다.

자바

로부터 Java 언어 사양 :

필드는 휘발성으로 선언 될 수 있으며,이 경우 Java 메모리 모델은 모든 스레드가 변수에 대해 일관된 값을 볼 수 있도록합니다.

씨#

volatile 키워드 에 대한 C # 참조에서 :

volatile 키워드는 운영 체제, 하드웨어 또는 동시에 실행되는 스레드와 같은 것으로 프로그램에서 필드를 수정할 수 있음을 나타냅니다.


Java에서 이해하는 것처럼 스레드 컨텍스트에서 해당 변수를 잠그는 것과 같은 역할을하며 C #에서는 변수 값을 program뿐만 아니라 OS와 같은 외부 요인으로 변경할 수 있습니다. 잠금이 암시되지 않음) ... 그 차이점을 올바르게 이해하면 알려주십시오 ...
Mircea

Java의 @Mircea에는 잠금이 포함되어 있지 않으며 휘발성 변수의 최신 값이 사용되도록합니다.
krock

Java는 일종의 메모리 장벽을 약속합니까? 아니면 C ++ 및 C #과 마찬가지로 참조를 최적화하지 않겠다고 약속합니까?
Steven Sudit

메모리 장벽은 구현 세부 사항입니다. Java가 실제로 약속하는 것은 모든 읽기가 가장 최근의 쓰기에 의해 쓰여진 가치를 볼 것이라는 것입니다.
Stephen C

1
@StevenSudit 예, 하드웨어에 장벽이 있거나로드 / 획득 또는 저장 / 릴리스가 필요한 경우 해당 지침을 사용합니다. 내 대답을 참조하십시오.
doug65536

9

Java에서 "휘발성"은 JVM에 변수가 동시에 여러 스레드에서 사용될 수 있음을 알리는 데 사용되므로 특정 공통 최적화를 적용 할 수 없습니다.

특히 동일한 변수에 액세스하는 두 스레드가 동일한 시스템의 별도 CPU에서 실행되는 상황입니다. 메모리 액세스가 캐시 액세스보다 훨씬 느리기 때문에 CPU가 보유한 데이터를 적극적으로 캐시하는 것이 매우 일반적입니다. 즉 , CPU1에서 데이터가 업데이트 되면 캐시가 자체적으로 지울 때가 아니라 즉시 모든 캐시와 주 메모리로 이동해야 CPU2가 업데이트 된 값을 볼 수 있습니다 (다시 모든 캐시를 무시 함).


1

비 휘발성 데이터를 읽을 때 실행 스레드가 항상 업데이트 된 값을 얻거나 얻지 못할 수 있습니다. 그러나 개체가 일시적인 경우 스레드는 항상 최신 값을 갖습니다.


1
답을 바꾸어 줄 수 있습니까?
Anirudha Gupta

volatile 키워드는 캐시 된 값이 아닌 가장 최신의 값을 제공합니다.
Subhash Saini 2016 년

0

휘발성은 동시성 문제를 해결하고 있습니다. 해당 값을 동기화합니다. 이 키워드는 주로 스레딩에 사용됩니다. 여러 스레드가 동일한 변수를 업데이트하는 경우


1
나는 그것이 문제를 "해결"한다고 생각하지 않는다. 어떤 상황에서 도움이되는 도구입니다. 경쟁 조건에서와 같이 잠금이 필요한 상황에서는 변동성을 사용하지 마십시오.
Scratte
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.