2012-01-11 3 views
30

C++ 11에는 새로운 난수 생성기 엔진과 배포 함수가 있습니다. 스레드가 안전합니까? 단일 스레드 및 엔진을 여러 스레드에서 공유하는 경우 안전하며 여전히 임의의 숫자를 받게됩니까? 내가 찾고 있어요 시나리오는C++ 11 난수 생성기의 스레드 안전

void foo() { 
    std::mt19937_64 engine(static_cast<uint64_t> (system_clock::to_time_t(system_clock::now()))); 
    std::uniform_real_distribution<double> zeroToOne(0.0, 1.0); 
#pragma omp parallel for 
    for (int i = 0; i < 1000; i++) { 
     double a = zeroToOne(engine); 
    } 
} 

이 libdispatch를 사용 OpenMP를 또는

void foo() { 
    std::mt19937_64 engine(static_cast<uint64_t> (system_clock::to_time_t(system_clock::now()))); 
    std::uniform_real_distribution<double> zeroToOne(0.0, 1.0); 
    dispatch_apply(1000, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(size_t i) { 
     double a = zeroToOne(engine); 
    }); 
} 

를 사용하여, 같은 것입니다. 그들은 하지 스레드로부터 안전하고있는 documentation

답변

25

는 C++ 11 표준 라이브러리는 크게 스레드에 안전하지 않습니다. PRNG 객체에 대한 스레드 안전성 보장은 컨테이너와 동일합니다. PRNG 클래스는 모두 유사 -random이므로 확실한 현재 상태를 기반으로 결정 성있는 시퀀스를 생성하므로 실제로 포함 된 상태 외부의 모든 부분을 엿보거나 파고 할 공간이 없습니다. 사용자).

컨테이너를 안전하게 공유하려면 잠금 장치가 필요하므로 PRNG 개체를 잠 가야합니다. 이것은 느리고 비 결정적이 될 것입니다. 스레드 당 하나의 개체가 더 좋을 것입니다.

는 §17.6.5.9 [res.on.data.races]

1이 절 (1.10)를 구현 데이터 레이스를 방지한다 충족 요구 사항을 규정한다. 달리 명시되지 않는 한 모든 표준 라이브러리 함수는 각각의 요구 사항을 충족해야한다 ( ). 구현은 아래 명시된 것 이외의 경우에 데이터 경합을 방지하기 위해 일 수 있습니다.

2 C++ 표준 라이브러리 함수하여야한다가 아닌 객체가이를 포함하여 함수의 argu- ments를 통해 직접 또는 간접적으로 액세스하지 않는 한 현재 스레드가 아닌 다른 스레드에서 액세스 직접 또는 간접적으로 액세스 개체 (1.10).

3 C++ 표준 라이브러리 함수를 직접 또는 간접적으로 객체 객체가이를 포함하여 함수의 비 const를 인수를 통해 직접 또는 간접적으로 액세스하지 않는 한 현재 스레드가 아닌 다른 스레드에서 액세스 (1.10)을 수정할 수 없다.

4 [참고 :이 의미, 예를 들어, 구현은 동기화하지 않고 내부 목적으로 정적 객체를 사용할 수 없다는 것이 도 명시 적으로 객체 betweenthreads를 공유하지 않는 프로그램에서 데이터 경쟁을 야기 할 수 있기 때문이다. -endnote]

5는 C++ 표준 라이브러리 함수는 그 용기 요소의 사양 의해 요구되는 기능을 호출 제외 인수를 통해 또는 용기 인수 요소를 통하여 간접적으로 액세스 개체에 액세스하지 않는다.

6 표준 라이브러리 컨테이너 또는 문자열 멤버 함수를 호출하여 얻은 이터레이터에 대한 작업은 기본 컨테이너에 액세스 할 수 있지만이를 수정하면 안됩니다. [주의 : 특히, 반복자를 무효화하는 컨테이너 조작은 해당 컨테이너와 연관된 반복자의 조작과 충돌합니다. - end note]

7 객체가 사용자에게 보이지 않고 데이터 레이스로부터 보호되는 경우 구현은 스레드 사이에서 자체 내부 객체를 공유 할 수 있습니다.

8 달리 명시되지 않는 한 C++ 표준 라이브러리 함수는 작업이 사용자에게 표시되는 영향 (1.10) 인 경우 현재 스레드 내에서만 모든 작업을 수행해야합니다.

9 [참고 : 에 표시되는 부작용이없는 경우 구현을 병렬화하여 구현할 수 있습니다. - 끝 노트]

+0

기본적으로 스레드로부터 안전하지 않다고 생각했습니다. 분산 객체'std :: uniform_real_distribution zeroToOne (0.0, 1.0)'을 공유하고 스레드 당 하나의 엔진을 사용합니까? – user1139069

+0

@ user1139069 : 아니오, 안전하지 않습니다. 언뜻보기에 배포 객체 *는 내부 상태를 유지하지 않고 각 호출을 엔진 객체에 위임하기 만하면 작업을 수행 할 수 있지만 충분히 불규칙한 비트를 생성하지 않는 엔진을 두 번 호출해야 할 수도 있습니다. 하지만 두 번 (또는 한 번) 과잉 공격이 될 수 있으므로 과도한 임의 비트의 캐싱을 허용하는 것이 좋습니다. §26.5.1.6 \t "난수 분포 요구 사항"은 이것을 허용한다; 배포 객체에는 특히 각 호출마다 변경되는 상태가 있습니다. 따라서 잠금 목적으로 엔진의 일부로 취급해야합니다. – Potatoswatter

0

스레드 안전에 대한 언급을하지 않으며, 그래서 나는 생각한다.

+12

cppreference.com에 언급되지 않은 것은 그렇게하지 않습니다. – Potatoswatter

2

표준 (잘 N3242)은 무작위로 생성되는 레이스가 전혀 없음을 나타내지 만 (rand 제외), 그렇게하지 않습니다 (내가 놓친 것이 아니라면). 게다가 실제로는 아무 것도 얻지 않고 상대적으로 무거운 오버 헤드가 발생하기 때문에 (적어도 숫자 자체의 생성과 비교할 때) 쓰레드 세이브하는 데는 아무런 포인트가 없습니다.

게다가 스레드 당 하나씩 갖는 대신 하나의 공유 된 난수 생성기가있는 이점을 보지 못합니다. 각 스레드는 slighly 다르게 초기화됩니다 (예 : 다른 생성자의 결과 또는 현재 스레드 ID). 어쨌든 어쨌든 각각의 시퀀스를 생성하는 생성기에 의존하지는 않을 것입니다. 그래서 나는 이런 식으로 뭔가로 코드를 다시 작성할 것 (openmp을 위해, 단서에 대한 libdispatch는) :

void foo() { 
    #pragma omp parallel 
    { 
    //just an example, not sure if that is a good way too seed the generation 
    //but the principle should be clear 
    std::mt19937_64 engine((omp_get_thread_num() + 1) * static_cast<uint64_t>(system_clock::to_time_t(system_clock::now()))); 
    std::uniform_real_distribution<double> zeroToOne(0.0, 1.0); 
    #pragma omp for 
     for (int i = 0; i < 1000; i++) { 
      double a = zeroToOne(engine); 
     } 
    } 
} 
+1

실제로 다른 스레드에서 동일한 RNG를 읽는 경우 고정 된 시드에 대해서도 동일한 일련의 난수를 얻는 데 의존 할 수 없습니다. 예약하면 별도의 실행에서 다른 스레드의 RNG에 대한 액세스 순서가 다르기 때문입니다 . 따라서 * 특히 * 재현 가능한 난수열이 필요한 경우 스레드간에 RNG를 공유하면 안됩니다. – celtschk

+0

@celtschk : 그것은 동일한 순서를 얻는 방법을 정의하는 방법에 달려 있습니다. 나는 하나가 동일한 순서 (globaly)를 얻을 것이라고 말할 것이다. 그것은 스레드가 각각의 실행과 함께 다른 부분을 볼 것이라는 것이다. – Grizzly