thread_call_once

thread_call_once (한 번만 호출하기)

std::call_once는 여러 스레드에서 동시에 호출되어도 주어진 Callable 객체를 정확히 한 번만 실행하도록 보장하는 함수예요. std::once_flag 객체와 함께 사용하며, 공유 자원의 초기화나 설정 작업을 스레드 안전하게 수행할 때 유용해요. 예외가 발생하면 다음 호출에서 다시 시도되므로, 실패해도 재시도가 가능한 구조로 동작해요.

출처: cppreference

본문

개요

<mutex> 헤더에 정의되어 있어요.

Defined in header
template < class Callable , class ... Args > void call_once ( std :: once_flag & flag , Callable && f , Args && ... args ); (since C++11)

Callable 객체 f를 여러 스레드에서 동시에 호출해도 정확히 한 번만 실행해요.

상세 동작

  • std::call_once가 호출될 때 flag가 이미 f가 호출되었음을 나타내면, std::call_once는 즉시 반환해요. 이러한 호출을 수동적(passive) 호출이라고 해요.
  • 그렇지 않으면 std::call_onceINVOKE(std::forward<Callable>(f), std::forward<Args>(args)...)를 호출해요. std::thread 생성자나 std::async와 달리 인자를 이동하거나 복사하지 않아요. 다른 실행 스레드로 전송할 필요가 없기 때문이에요. 이러한 호출을 능동적(active) 호출이라고 해요.
  • 해당 호출이 예외를 던지면 예외는 std::call_once의 호출자에게 전파되고 flag는 바뀌지 않아서, 이후 다른 호출이 다시 시도돼요. 이러한 호출을 예외적(exceptional) 호출이라고 해요. 해당 호출이 정상적으로 반환되면 flag가 바뀌고, 같은 flag에 대한 다른 모든 std::call_once 호출은 수동적(passive) 호출이 되는 것이 보장돼요. 이러한 호출을 반환적(returning) 호출이라고 해요.

같은 flag에 대한 모든 능동적 호출은 0개 이상의 예외적 호출 뒤에 하나의 반환적 호출이 이어지는 단일 전체 순서를 형성해요. 각 능동적 호출의 종료는 그 순서에서 다음 능동적 호출과 동기화(synchronizes-with)돼요.

반환적 호출의 반환은 같은 flag에 대한 모든 수동적 호출의 반환과 동기화돼요. 즉, std::call_once에 대한 모든 동시 호출은 추가 동기화 없이 능동적 호출이 만든 모든 부수 효과를 관찰할 수 있음을 보장해요.

매개변수

flag - 정확히 하나의 함수가 실행되는 객체
f - 호출할 Callable 객체
args... - 함수에 전달할 인자들

반환값

(없음)

예외

  • std::call_once 호출이 지정된 대로 실행되지 못하게 하는 조건이 있으면 std::system_error가 발생해요.
  • f가 던지는 모든 예외가 전파돼요.

참고 사항

동시에 std::call_once를 호출할 때 서로 다른 함수 f를 전달하면 어떤 f가 호출될지는 지정되지 않아요. 선택된 함수는 해당 std::call_once 호출이 실행된 동일한 스레드에서 실행돼요.

함수 로컬 정적 변수의 초기화는 여러 스레드에서 호출되어도 정확히 한 번만 발생하는 것이 보장되며, std::call_once를 사용한 동등한 코드보다 더 효율적일 수 있어요.

이 함수의 POSIX 등가물은 pthread_once예요.

예제

#include <iostream>
#include <mutex>
#include <thread>

std::once_flag flag1, flag2;

void simple_do_once()
{
    std::call_once(flag1, [](){ std::cout << "Simple example: called once\n"; });
}

void may_throw_function(bool do_throw)
{
    if (do_throw)
    {
        std::cout << "Throw: call_once will retry\n"; // this may appear more than once
        throw std::exception();
    }
    std::cout << "Did not throw, call_once will not attempt again\n"; // guaranteed once
}

void do_once(bool do_throw)
{
    try
    {
        std::call_once(flag2, may_throw_function, do_throw);
    }
    catch (...) {}
}

int main()
{
    std::thread st1(simple_do_once);
    std::thread st2(simple_do_once);
    std::thread st3(simple_do_once);
    std::thread st4(simple_do_once);
    st1.join();
    st2.join();
    st3.join();
    st4.join();
    
    std::thread t1(do_once, true);
    std::thread t2(do_once, true);
    std::thread t3(do_once, false);
    std::thread t4(do_once, true);
    t1.join();
    t2.join();
    t3.join();
    t4.join();
}

가능한 출력:

Simple example: called once
Throw: call_once will retry
Throw: call_once will retry
Throw: call_once will retry
Did not throw, call_once will not attempt again

결함 보고서

다음 동작 변경 결함 보고서는 이전에 게시된 C++ 표준에 소급 적용되었어요.

DR 적용 대상 게시된 동작 올바른 동작
LWG 2080 C++11 f가 유효하지 않으면 std::invalid_argument가 던져지지만, f가 무효화되는 시나리오는 지정되지 않음 이 오류 조건을 제거함
LWG 2442 C++11 인자가 호출 전에 복사 및/또는 이동됨 복사/이동이 수행되지 않음

같이 보기

once_flag (C++11) call_once가 함수를 한 번만 호출하도록 보장하는 헬퍼 객체 (클래스) [편집]
C documentation for call_once

더 알아보기 (Learn more)

cppreference