컴파일러가 사용하지 않는 정적 thread_local 클래스 멤버를 무시 했습니까?


10

클래스에서 스레드 등록을 원하므로 thread_local기능 검사를 추가하기로 결정했습니다 .

#include <iostream>
#include <thread>

class Foo {
 public:
  Foo() {
    std::cout << "Foo()" << std::endl;
  }
  ~Foo() {
    std::cout << "~Foo()" << std::endl;
  }
};

class Bar {
 public:
  Bar() {
    std::cout << "Bar()" << std::endl;
    //foo;
  }
  ~Bar() {
    std::cout << "~Bar()" << std::endl;
  }
 private:
  static thread_local Foo foo;
};

thread_local Foo Bar::foo;

void worker() {
  {
    std::cout << "enter block" << std::endl;
    Bar bar1;
    Bar bar2;
    std::cout << "exit block" << std::endl;
  }
}

int main() {
  std::thread t1(worker);
  std::thread t2(worker);
  t1.join();
  t2.join();
  std::cout << "thread died" << std::endl;
}

코드는 간단하다. 내 Bar클래스에는 정적 thread_local멤버가 foo있습니다. 정적 thread_local Foo foo이 작성되면 스레드가 작성되었음을 의미합니다.

그러나 코드를 실행할 때 Foo()인쇄물에 아무것도 없으며을 Bar사용하는 생성자 에서 주석을 제거 foo하면 코드가 올바르게 작동합니다.

나는 GCC (7.4.0)와 Clang (6.0.0)에서 이것을 시도했으며 결과는 동일합니다. 컴파일러 foo가 사용되지 않은 것을 발견 하고 인스턴스를 생성하지 않는다고 생각합니다. 그래서

  1. 컴파일러가 static thread_local멤버를 무시 했습니까 ? 이것을 어떻게 디버깅 할 수 있습니까?
  2. 그렇다면 왜 일반 static회원에게이 문제가없는 것입니까?

답변:


9

관찰에는 문제가 없습니다. [basic.stc.static] / 2 는 정적 저장 기간이있는 변수를 제거하는 것을 금지합니다.

정적 저장 기간을 가진 변수에 초기화 또는 부작용이있는 소멸자가있는 경우 클래스 객체 또는 해당 복사 / 이동이 [class.copy]에 지정된대로 제거 될 수 있다는 점을 제외하고는 사용되지 않는 것처럼 보이더라도 제거되지 않아야합니다. .

다른 저장 기간에는이 제한이 없습니다. 실제로 [basic.stc.thread] / 2 는 다음과 같이 말합니다.

쓰레드 저장 시간을 갖는 변수는 첫 번째 사용 전에 초기화되어야하며, 구성된다면 쓰레드 출구에서 파괴되어야한다.

이는 스레드 저장 기간이있는 변수는 odr을 사용하지 않는 한 구성 할 필요가 없음을 나타냅니다.


그러나 왜이 차이가 있습니까?

정적 저장 기간 동안 프로그램 당 하나의 변수 인스턴스 만 있습니다. 그것의 구성의 부작용은 중요 할 수 있고 (프로그램 전체의 생성자와 유사하다), 부작용이 요구된다.

그러나 스레드 로컬 저장 기간 동안 문제가 있습니다. 알고리즘이 많은 스레드를 시작할 수 있습니다. 이러한 스레드 대부분의 경우 변수는 완전히 관련이 없습니다. 호출하는 외부 물리 시뮬레이션 라이브러리가 std::reduce(std::execution::par_unseq, first, last)결국 많은 것을 만들어내는 것이 재미있을 것 입니다.foo 인스턴스를 있을까요?

물론, 사용하지 않는 스레드 로컬 스토리지 기간의 변수 구성의 부작용에 대한 합법적 인 사용이있을 수 있습니다 (예 : 스레드 추적기). 그러나, 이것을 보장 하는 이점 은 전술 한 단점을 보상하기에 충분하지 않으므로, 이들 변수는 사용되지 않는 한 제거 될 수있다. (컴파일러는하지 않기로 선택할 수 있습니다. 또한 std::thread이를 처리하는 자체 래퍼를 만들 수도 있습니다 .)


1
좋아요 .. 표준이 그렇게 말한 것보다 ..하지만위원회가 부작용을 정적 저장 기간으로 간주하지 않는 것이 이상하지 않습니까?
ravenisadesk

@reavenisadesk 업데이트 된 답변보기.
LF

1

이 정보는 " LFF 스레드 처리를위한 ELF 처리 "에서 @LF의 답변을 증명할 수 있습니다.

또한 런타임 지원은 스레드 로컬 스토리지가 필요하지 않은 경우 작성하지 않아야합니다. 예를 들어,로드 된 모듈은 프로세스를 구성하는 많은 스레드 중 하나에서만 사용할 수 있습니다. 모든 스레드에 스토리지를 할당하는 것은 메모리와 시간 낭비입니다. 게으른 방법이 필요합니다. 동적으로로드 된 객체를 처리하려면 이미 할당되지 않은 스토리지를 인식해야하므로 추가 부담이 없습니다. 이것은 모든 스레드를 중지하고 다시 실행하기 전에 모든 스레드에 스토리지를 할당하는 유일한 대안입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.