cout은 동기화 / 스레드 안전합니까?


112

일반적으로 스트림이 동기화되지 않는다고 가정하고 적절한 잠금을 수행하는 것은 사용자에게 달려 있습니다. 그러나 cout표준 라이브러리에서 특별한 대우를받는 것과 같은 것들이 있습니까?

즉, 여러 스레드가 쓰는 cout경우 cout개체가 손상 될 수 있습니까? 동기화하더라도 무작위로 인터리브 출력을 얻을 수 있지만 인터리빙은 보장됩니다. 즉, cout여러 스레드에서 사용하는 것이 안전 합니까?

이 공급 업체에 종속적입니까? gcc는 무엇을합니까?


중요 : 이에 대한 증거가 필요하므로 "예"라고 답한 경우 답변에 대한 참조를 제공하십시오.

내 관심사는 또한 기본 시스템 호출에 관한 것이 아니라 괜찮지 만 스트림이 위에 버퍼링 레이어를 추가합니다.


2
이는 공급 업체에 따라 다릅니다. C ++ (C ++ 0x 이전)에는 다중 스레드 개념이 없습니다.
Sven

2
C ++ 0x는 어떻습니까? 그것은 메모리 모델과 스레드가 무엇인지를 정의합니다. 그래서 아마도 이러한 것들이 출력에 떨어질까요?
rubenvb 2011 년

2
스레드로부터 안전하게 만드는 공급 업체가 있습니까?
edA-qa mort-ora-y

누구든지 가장 최근의 C ++ 2011 제안 표준에 대한 링크가 있습니까?
edA-qa mort-ora-y

4
어떤 의미에서 이것은 완전한 출력이 한 번에 기록 될 때 printf 을 발합니다 stdout. std::cout표현식 체인의 각 링크를 사용하면 개별적으로 출력됩니다 stdout. 그들 사이 stdout에는 최종 출력의 순서가 엉망이되는 다른 스레드가 쓸 수 있습니다 .
legends2k

답변:


106

C ++ 03 표준은 그것에 대해 아무 말도하지 않습니다. 스레드 안전성에 대한 보장이없는 경우 스레드 안전성이 아닌 것으로 간주해야합니다.

여기서 특히 흥미로운 cout것은 버퍼링 된 사실입니다 . 호출 write(또는 특정 구현에서 해당 효과를 달성하는 것이 무엇이든간에)이 상호 배타적 인 것으로 보장 되더라도 버퍼는 다른 스레드에서 공유 될 수 있습니다. 이것은 스트림의 내부 상태를 빠르게 손상시킬 것입니다.

그리고 버퍼에 대한 액세스가 스레드로부터 안전하다고 보장하더라도이 코드에서 어떤 일이 발생할 것이라고 생각하십니까?

// in one thread
cout << "The operation took " << result << " seconds.";

// in another thread
cout << "Hello world! Hello " << name << "!";

여기에있는 각 줄이 상호 배제 적으로 작동하기를 원할 것입니다. 그러나 구현이이를 어떻게 보장 할 수 있습니까?

C ++ 11에서는 몇 가지 보증이 있습니다. FDIS는 §27.4.1 [iostream.objects.overview]에서 다음과 같이 말합니다.

동기화 된 (§27.5.3.4) 표준 iostream 개체의 형식화 및 형식화되지 않은 입력 (§27.7.2.1) 및 출력 (§27.7.3.1) 함수 또는 여러 스레드에 의한 표준 C 스트림에 대한 동시 액세스는 데이터 경쟁 (§ 1.10). [참고 : 사용자가 인터리브 문자를 피하려면 여러 스레드에서 이러한 개체 및 스트림의 동시 사용을 동기화해야합니다. — 끝 참고]

따라서 손상된 스트림을 얻지는 않지만 출력이 가비지가되지 않도록하려면 수동으로 동기화해야합니다.


2
기술적으로 C ++ 98 / C ++ 03의 경우 사실이지만 모두가 알고 있다고 생각합니다. 그러나 이것은 두 가지 흥미로운 질문에 대답하지 않습니다. C ++ 0x는 어떻습니까? 일반적인 구현은 실제로 무엇을 합니까?
Nemo

1
@ edA-qa mort-ora-y : 아니요, 틀 렸습니다. C ++ 11은 표준 스트림 개체 동기화 할 수 있고 잘 정의 된 동작을 유지할 있음을 명확하게 정의합니다 .
ildjarn

12
@ildjarn-아니요, @ edA-qa mort-ora-y가 맞습니다. 만큼 cout.sync_with_stdio()사용 사실이다 cout추가적인 동기화없이 여러 스레드에서 출력 문자에 잘 정의이지만, 개별 바이트 수준. 따라서, cout << "ab";cout << "cd"다른 스레드의 실행 월 출력 acdb예를 들어, 수 있지만하지 불확정 동작 원인.
JohannesD

4
@JohannesD : 우리는 거기에 동의합니다-기본 C API와 동기화됩니다. 내 요점은 유용한 방식으로 "동기화"되지 않는다는 것입니다. 즉, 가비지 데이터를 원하지 않는 경우 여전히 수동 동기화가 필요합니다.
ildjarn

2
@ildjarn, 나는 쓰레기 데이터에 대해 괜찮습니다. 지금은 분명해 보이는 데이터 경합 상태에 관심이 있습니다.
edA-qa mort-ora-y

16

이것은 좋은 질문입니다.

첫째, C ++ 98 / C ++ 03에는 "스레드"개념이 없습니다. 그래서 그 세상에서 질문은 무의미합니다.

C ++ 0x는 어떻습니까? Martinho의 답변을 참조하십시오 (나는 놀랐습니다).

C ++ 0x 이전의 특정 구현은 어떻습니까? 예를 들어 다음은 basic_streambuf<...>:sputcGCC 4.5.2 ( "streambuf"헤더) 의 소스 코드입니다 .

 int_type
 sputc(char_type __c)
 {
   int_type __ret;
   if (__builtin_expect(this->pptr() < this->epptr(), true)) {
       *this->pptr() = __c;
        this->pbump(1);
        __ret = traits_type::to_int_type(__c);
      }
    else
        __ret = this->overflow(traits_type::to_int_type(__c));
    return __ret;
 }

분명히 이것은 잠금을 수행하지 않습니다. 그리고 둘 다 그렇지 않습니다 xsputn. 그리고 이것은 확실히 cout이 사용하는 streambuf 유형입니다.

내가 알 수있는 한 libstdc ++는 스트림 작업에 대해 잠금을 수행하지 않습니다. 그리고 나는 느리기 때문에 어떤 것도 기대하지 않을 것입니다.

그래서이 구현으로, 분명히 다른 각 손상 (두 개의 스레드 '출력 가능 하지 만 인터리브).

이 코드가 데이터 구조 자체를 손상시킬 수 있습니까? 대답은 이러한 기능의 가능한 상호 작용에 따라 다릅니다. 예를 들어, 한 스레드가 버퍼를 플러시하려고하는 동안 다른 스레드가 호출을 시도하면 어떻게됩니까 xsputn? 컴파일러와 CPU가 메모리로드 및 저장 순서를 재정렬하는 방법에 따라 달라질 수 있습니다. 확인하려면 신중한 분석이 필요합니다. 또한 두 스레드가 동시에 동일한 위치를 수정하려고 할 때 CPU가 수행하는 작업에 따라 다릅니다.

즉, 현재 환경에서 제대로 작동하더라도 런타임, 컴파일러 또는 CPU를 업데이트하면 중단 될 수 있습니다.

요약 : "하지 않을 것". 적절한 잠금을 수행하는 로깅 클래스를 빌드하거나 C ++ 0x로 이동하십시오.

약한 대안으로 cout을 unbuffered로 설정할 수 있습니다. (보장되지는 않지만) 버퍼와 관련된 모든 로직을 건너 뛰고 write직접 호출 할 가능성이 있습니다. 엄청나게 느릴 수 있지만.


1
좋은 대답이지만 C ++ 11이 .NET에 대한 동기화를 정의한다는 것을 보여주는 Martinho의 응답을 살펴보십시오 cout.
edA-qa mort-ora-y

7

C ++ 표준은 스트림에 쓰기가 스레드로부터 안전한지 여부를 지정하지 않지만 일반적으로 그렇지 않습니다.

www.techrepublic.com/article/use-stl-streams-for-easy-c-plus-plus-thread-safe-logging

또한 : C ++의 표준 출력 스트림이 스레드로부터 안전합니까 (cout, cerr, clog)?

최신 정보

@Martinho Fernandes의 답변을 살펴보고 새로운 표준 C ++ 11이 이에 대해 무엇을 알려 주는지 알아보십시오.


3
나는 C ++ 11이 이제 표준이기 때문에이 대답은 실제로 잘못되었습니다.
edA-qa mort-ora-y 2011

6

다른 답변에서 언급했듯이 C ++ 표준이 스레딩에 대해 언급하지 않기 때문에 이것은 확실히 공급 업체에 따라 다릅니다 (이는 C ++ 0x에서 변경됨).

GCC는 스레드 안전성과 I / O에 대해 많은 약속을하지 않습니다. 그러나 그것이 무엇을 약속하는지에 대한 문서는 여기에 있습니다.

핵심은 아마 :

__basic_file 유형은 단순히 C stdio 계층 주위의 작은 래퍼 모음입니다 (다시 구조 아래의 링크 참조). 우리는 스스로를 잠그지 않고 단순히 fopen, fwrite 등의 호출을 통과합니다.

따라서 3.0의 경우 "I / O에 대한 다중 스레딩 안전"이라는 질문에 "귀하의 플랫폼의 C 라이브러리가 I / O에 대해 스레드 안전합니까?"라는 질문에 답해야합니다. 일부는 기본적으로, 일부는 그렇지 않습니다. 많은 사람들이 스레드 안전성과 효율성의 다양한 장단점을 가진 C 라이브러리의 여러 구현을 제공합니다. 프로그래머는 항상 여러 스레드를 관리해야합니다.

(예를 들어 POSIX 표준에서는 C stdio FILE * 작업이 원자 적이어야합니다. POSIX를 준수하는 C 라이브러리 (예 : Solaris 및 GNU / Linux)에는 FILE *에 대한 작업을 직렬화하는 내부 뮤텍스가 있습니다. 그러나 여전히 필요합니다. 한 스레드에서 fclose (fs)를 호출하고 다른 스레드에서 fs에 액세스하는 것과 같은 어리석은 일을하지 마십시오.)

따라서 플랫폼의 C 라이브러리가 스레드 세이프 인 경우 fstream I / O 작업은 최하위 수준에서 스레드 세이프됩니다. 스트림 형식화 클래스에 포함 된 데이터 조작 (예 : std :: ofstream 내부에 콜백 설정)과 같은 상위 수준 작업의 경우 다른 중요한 공유 리소스처럼 이러한 액세스를 보호해야합니다.

언급 한 3.0 기간 동안 사인이 바뀌 었는지 모르겠습니다.

MSVC의 스레드 안전 문서는 http://msdn.microsoft.com/en-us/library/c9ceah3b.aspxiostreams 에서 찾을 수 있습니다 .

단일 개체는 여러 스레드에서 읽을 수있는 스레드로부터 안전합니다. 예를 들어, 객체 A가 주어지면 스레드 1과 스레드 2에서 동시에 A를 읽는 것이 안전합니다.

하나의 스레드가 단일 객체에 쓰는 경우 동일한 스레드 또는 다른 스레드에서 해당 객체에 대한 모든 읽기 및 쓰기를 보호해야합니다. 예를 들어, 객체 A가 주어지면 스레드 1이 A에 쓰고 있다면 스레드 2는 A에서 읽거나 쓰지 못하도록해야합니다.

다른 스레드가 동일한 유형의 다른 인스턴스를 읽거나 쓰는 경우에도 한 유형의 인스턴스를 읽고 쓰는 것이 안전합니다. 예를 들어 동일한 유형의 객체 A와 B가 주어지면 A가 스레드 1에 기록되고 B가 스레드 2에서 읽혀지면 안전합니다.

...

iostream 클래스

iostream 클래스는 한 가지를 제외하고는 다른 클래스와 동일한 규칙을 따릅니다. 여러 스레드에서 개체에 쓰는 것이 안전합니다. 예를 들어, 스레드 1은 스레드 2와 동시에 cout에 쓸 수 있습니다. 그러나 이로 인해 두 스레드의 출력이 혼합 될 수 있습니다.

참고 : 스트림 버퍼에서 읽는 것은 읽기 작업으로 간주되지 않습니다. 이것은 클래스의 상태를 변경하기 때문에 쓰기 작업으로 간주되어야합니다.

정보는 MSVC의 최신 버전 (현재 VS 2010 / MSVC 10 / cl.exe16.x 용)에 대한 것입니다. 페이지의 드롭 다운 컨트롤을 사용하여 이전 버전의 MSVC에 대한 정보를 선택할 수 있습니다 (이전 버전의 정보는 다름).


1
"언급 한 3.0 기간 동안 어떤 것이 사인을 바꾸 었는지 모르겠습니다." 확실히 그랬습니다. 지난 몇 년 동안 g ++ 스트림 구현은 자체 버퍼링을 수행했습니다.
Nemo
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.