수색…


통사론

  • 실()
  • 스레드 (스레드 && 기타)
  • 명시 적 스레드 (함수 && func, Args && ... args)

매개 변수

매개 변수 세부
other other 소유권을 가지며, other 는 더 이상 스레드를 소유하지 않습니다.
func 별도의 스레드에서 호출하는 함수
args func 대한 인수

비고

몇 가지 메모 :

  • 두 개의 std::thread 객체는 결코 같은 스레드를 나타낼 수 없습니다 .
  • std::thread 객체는 어떤 스레드도 나타내지 않는 상태에있을 수 있습니다 (예 : 이동 후, join 호출 후 등).

스레드 작업

스레드를 시작하면 스레드가 완료 될 때까지 실행됩니다.

종종 어떤 시점에서 예를 들어 결과를 사용하기를 원하기 때문에 스레드가 완료 될 때까지 대기해야합니다 (아마도 스레드가 이미 완료되었을 수 있음).

int n;
std::thread thread{ calculateSomething, std::ref(n) };

//Doing some other stuff

//We need 'n' now!
//Wait for the thread to finish - if it is not already done
thread.join();

//Now 'n' has the result of the calculation done in the seperate thread
std::cout << n << '\n';

또한 스레드를 detach 하여 자유롭게 실행할 수 있습니다.

std::thread thread{ doSomething };

//Detaching the thread, we don't need it anymore (for whatever reason)
thread.detach();

//The thread will terminate when it is done, or when the main thread returns

스레드에 대한 참조 전달

표준 (또는 const 참조)을 std::thread 가 복사 / 이동하기 때문에 스레드에 직접 전달할 수는 없습니다. 대신 std::reference_wrapper 사용 std::reference_wrapper .

void foo(int& b)
{
    b = 10;
}

int a = 1;
std::thread thread{ foo, std::ref(a) }; //'a' is now really passed as reference

thread.join();
std::cout << a << '\n'; //Outputs 10

void bar(const ComplexObject& co)
{
    co.doCalculations();
}

ComplexObject object;
std::thread thread{ bar, std::cref(object) }; //'object' is passed as const&

thread.join();
std::cout << object.getResult() << '\n'; //Outputs the result

std :: thread 만들기

C ++에서 스레드는 std :: thread 클래스를 사용하여 생성됩니다. 스레드는 별도의 실행 흐름입니다. 헬퍼가 다른 작업을 수행하는 동안 하나의 작업을 수행하는 것과 유사합니다. 스레드의 모든 코드가 실행되면 종료됩니다 . 쓰레드를 생성 할 때, 쓰레드를 실행시켜야합니다. 스레드에 전달할 수있는 몇 가지 사항은 다음과 같습니다.

  • 자유 함수
  • 멤버 함수
  • 펑터 객체
  • 람다 식

자유 함수 예제 - 별도의 스레드에서 함수를 실행합니다 ( 라이브 예제 ).

#include <iostream>
#include <thread>
 
void foo(int a)
{
    std::cout << a << '\n';
}
 
int main()
{
    // Create and execute the thread
    std::thread thread(foo, 10); // foo is the function to execute, 10 is the
                                 // argument to pass to it
 
    // Keep going; the thread is executed separately
 
    // Wait for the thread to finish; we stay here until it is done
    thread.join();
 
    return 0;
}

멤버 함수 예제 - 별도의 스레드에서 멤버 함수를 실행합니다 ( 라이브 예제 ).

#include <iostream>
#include <thread>
 
class Bar
{
public:
    void foo(int a)
    {
        std::cout << a << '\n';
    }
};
 
int main()
{
    Bar bar;
    
    // Create and execute the thread
    std::thread thread(&Bar::foo, &bar, 10); // Pass 10 to member function
 
    // The member function will be executed in a separate thread
 
    // Wait for the thread to finish, this is a blocking operation
    thread.join();
 
    return 0;
}

Functor 객체 예제 (실제 예제 ) :

#include <iostream>
#include <thread>
 
class Bar
{
public:
    void operator()(int a)
    {
        std::cout << a << '\n';
    }
};
 
int main()
{
    Bar bar;
    
    // Create and execute the thread
    std::thread thread(bar, 10); // Pass 10 to functor object
 
    // The functor object will be executed in a separate thread
 
    // Wait for the thread to finish, this is a blocking operation
    thread.join();
 
    return 0;
}

람다 표현식 예제 ( 실례 ) :

#include <iostream>
#include <thread>
 
int main()
{
    auto lambda = [](int a) { std::cout << a << '\n'; };

    // Create and execute the thread
    std::thread thread(lambda, 10); // Pass 10 to the lambda expression
 
    // The lambda expression will be executed in a separate thread
 
    // Wait for the thread to finish, this is a blocking operation
    thread.join();
 
    return 0;
}

현재 스레드의 작업

std::this_thread 는 호출 된 함수에서 현재 스레드에서 흥미로운 것을 수행하는 함수가있는 namespace 입니다.

기능 기술
get_id thread의 ID를 돌려줍니다.
sleep_for 지정된 시간 동안 잠자기 상태가됩니다.
sleep_until 특정 시간까지 잠자기
yield 실행중인 스레드를 다시 예약하여 다른 스레드에 우선 순위 부여

std::this_thread::get_id 사용하여 현재 스레드 ID std::this_thread::get_id :

void foo()
{
    //Print this threads id
    std::cout << std::this_thread::get_id() << '\n';
}

std::thread thread{ foo };
thread.join(); //'threads' id has now been printed, should be something like 12556

foo(); //The id of the main thread is printed, should be something like 2420

std::this_thread::sleep_for 사용하여 3 초 동안 std::this_thread::sleep_for :

void foo()
{
    std::this_thread::sleep_for(std::chrono::seconds(3));
}

std::thread thread{ foo };
foo.join();

std::cout << "Waited for 3 seconds!\n";

앞으로 3 시간 동안 std::this_thread::sleep_until 사용하여 std::this_thread::sleep_until :

void foo()
{
    std::this_thread::sleep_until(std::chrono::system_clock::now() + std::chrono::hours(3));
}

std::thread thread{ foo };
thread.join();

std::cout << "We are now located 3 hours after the thread has been called\n";

std::this_thread::yield 사용하여 다른 스레드에 우선 순위를 부여하게 :

void foo(int a)
{
    for (int i = 0; i < al ++i)
        std::this_thread::yield(); //Now other threads take priority, because this thread
                                   //isn't doing anything important

    std::cout << "Hello World!\n";
}

std::thread thread{ foo, 10 };
thread.join();

std :: thread 대신에 std :: async 사용하기

std::async 는 스레드를 만들 수도 있습니다. std::thread 와 비교해 보면 덜 강력하지만 사용하기 쉬운 것으로 간주됩니다. 단지 비동기 적으로 함수를 실행하려고 할 때입니다.

함수를 비동기 적으로 호출

#include <future>
#include <iostream>

unsigned int square(unsigned int i){
    return i*i;
}

int main() {
    auto f = std::async(std::launch::async, square, 8);
    std::cout << "square currently running\n"; //do something while square is running
    std::cout << "result is " << f.get() << '\n'; //getting the result from square
}

일반적인 함정

  • std::async 는 함수에 의해 계산 될 반환 값을 저장하는 std::future 를 반환합니다. 그 future 가 파괴되면 스레드가 완료 될 때까지 기다려 코드를 효과적으로 단일 스레드로 만듭니다. 반환 값이 필요하지 않을 때 쉽게 간과됩니다.

    std::async(std::launch::async, square, 5);
    //thread already completed at this point, because the returning future got destroyed
    
  • std::async 는 실행 정책없이 작동하므로 std::async(square, 5); 컴파일합니다. 그렇게하면 시스템은 스레드를 만들지 여부를 결정하게됩니다. 시스템이 효율적으로 실행할 수있는 것보다 많은 스레드를 이미 실행하고 있지 않는 한, 시스템이 스레드를 만들기로 결정한 것입니다. 안타깝게도 구현은 일반적으로 그 상황에서 스레드를 생성하지 않기로 선택하므로 std::launch::async 해당 동작을 재정 의하여 시스템이 스레드를 생성하도록해야합니다.

  • 경쟁 조건을 조심하십시오.

선물과 약속 에 대한 비동기에 대한 추가 정보

스레드가 항상 조인되는지 확인

std::thread 대한 소멸자가 호출되면 join() 또는 detach() 대한 호출이 수행 되어야합니다 . 스레드가 조인되거나 분리되지 않은 경우 기본적으로 std::terminate 가 호출됩니다. RAII를 사용하면 일반적으로 이렇게 간단합니다.

class thread_joiner
{
public:

    thread_joiner(std::thread t)
        : t_(std::move(t))
    { }

    ~thread_joiner()
    {
        if(t_.joinable()) {
            t_.join();
        }
    }

private:

    std::thread t_;
}

이것은 다음과 같이 사용됩니다 :

 void perform_work()
 {
     // Perform some work
 }

 void t()
 {
     thread_joiner j{std::thread(perform_work)};
     // Do some other calculations while thread is running
 } // Thread is automatically joined here

이것은 또한 예외 안전을 제공합니다. 우리가 쓰레드를 정상적으로 생성했고 다른 계산을 수행하는 t() 에서 수행 된 작업이 예외를 던지면 join() 은 결코 쓰레드에서 호출되지 않았을 것이고 프로세스는 종료되었을 것입니다.

스레드 객체 재 할당

빈 스레드 객체를 만들어 나중에 작업을 할당 할 수 있습니다.

스레드 오브젝트를 다른 활성, joinable 스레드에 할당하면 스레드가 대체되기 전에 std::terminate 가 자동으로 호출됩니다.

#include <thread>

void foo()
{
    std::this_thread::sleep_for(std::chrono::seconds(3));
}
//create 100 thread objects that do nothing
std::thread executors[100];

// Some code

// I want to create some threads now

for (int i = 0;i < 100;i++)
{
    // If this object doesn't have a thread assigned
    if (!executors[i].joinable())
         executors[i] = std::thread(foo);
}

기본 동기화

스레드 동기화는 다른 동기화 프리미티브 중에서 뮤텍스를 사용하여 수행 할 수 있습니다. 표준 라이브러리에서 제공하는 여러 가지 뮤텍스 유형이 있지만 가장 단순한 것은 std::mutex 입니다. 뮤텍스를 잠 그려면 뮤텍스를 구성하십시오. 가장 간단한 잠금 유형은 std::lock_guard .

std::mutex m;
void worker() {
    std::lock_guard<std::mutex> guard(m); // Acquires a lock on the mutex
    // Synchronized code here
} // the mutex is automatically released when guard goes out of scope

std::lock_guard 하면 뮤텍스가 잠금 객체의 전체 수명 동안 잠겨집니다. 영역을 수동으로 잠금 제어해야하는 경우 대신 std::unique_lock 사용하십시오.

std::mutex m;
void worker() {
    // by default, constructing a unique_lock from a mutex will lock the mutex
    // by passing the std::defer_lock as a second argument, we
    // can construct the guard in an unlocked state instead and
    // manually lock later.
    std::unique_lock<std::mutex> guard(m, std::defer_lock);
    // the mutex is not locked yet!
    guard.lock();
    // critical section
    guard.unlock();
    // mutex is again released
}

더 많은 스레드 동기화 구조

조건 변수 사용

조건 변수는 스레드 간의 통신을 조정하기 위해 뮤텍스와 함께 사용되는 프리미티브입니다. 이 작업을 수행하는 것이 배타적이거나 가장 효율적인 방법은 아니지만 패턴을 잘 아는 사람들에게는 가장 간단한 방법 일 수 있습니다.

하나는 std::unique_lock<std::mutex>std::condition_variable 을 기다린다. 이를 통해 코드는 획득 진행 여부를 결정하기 전에 공유 상태를 안전하게 검사 할 수 있습니다.

아래는 std::thread , std::condition_variable , std::mutex 및 몇 가지 다른 것들을 사용하여 프로듀서 - 소비자 스케치입니다.

#include <condition_variable>
#include <cstddef>
#include <iostream>
#include <mutex>
#include <queue>
#include <random>
#include <thread>


int main()
{
    std::condition_variable cond;
    std::mutex mtx;
    std::queue<int> intq;
    bool stopped = false;

    std::thread producer{[&]()
    {
        // Prepare a random number generator.
        // Our producer will simply push random numbers to intq.
        //
        std::default_random_engine gen{};
        std::uniform_int_distribution<int> dist{};

        std::size_t count = 4006;    
        while(count--)
        {    
            // Always lock before changing
            // state guarded by a mutex and
            // condition_variable (a.k.a. "condvar").
            std::lock_guard<std::mutex> L{mtx};

            // Push a random int into the queue
            intq.push(dist(gen));

            // Tell the consumer it has an int
            cond.notify_one();
        }

        // All done.
        // Acquire the lock, set the stopped flag,
        // then inform the consumer.
        std::lock_guard<std::mutex> L{mtx};

        std::cout << "Producer is done!" << std::endl;

        stopped = true;
        cond.notify_one();
    }};

    std::thread consumer{[&]()
    {
        do{
            std::unique_lock<std::mutex> L{mtx};
            cond.wait(L,[&]()
            {
                // Acquire the lock only if
                // we've stopped or the queue
                // isn't empty
                return stopped || ! intq.empty();
            });

            // We own the mutex here; pop the queue
            // until it empties out.

            while( ! intq.empty())
            {
                const auto val = intq.front();
                intq.pop();

                std::cout << "Consumer popped: " << val << std::endl;
            }

            if(stopped){
                // producer has signaled a stop
                std::cout << "Consumer is done!" << std::endl;
                break;
            }

        }while(true);
    }};

    consumer.join();
    producer.join();
    
    std::cout << "Example Completed!" << std::endl;

    return 0;
}

간단한 스레드 풀 만들기

C ++ 11 스레딩 프리미티브는 여전히 상대적으로 낮은 수준입니다. 그것들은 쓰레드 풀처럼 더 높은 수준의 구조체를 작성하는데 사용될 수 있습니다 :

C ++ 14
struct tasks {
  // the mutex, condition variable and deque form a single
  // thread-safe triggered queue of tasks:
  std::mutex m;
  std::condition_variable v;
  // note that a packaged_task<void> can store a packaged_task<R>:
  std::deque<std::packaged_task<void()>> work;

  // this holds futures representing the worker threads being done:
  std::vector<std::future<void>> finished;

  // queue( lambda ) will enqueue the lambda into the tasks for the threads
  // to use.  A future of the type the lambda returns is given to let you get
  // the result out.
  template<class F, class R=std::result_of_t<F&()>>
  std::future<R> queue(F&& f) {
    // wrap the function object into a packaged task, splitting
    // execution from the return value:
    std::packaged_task<R()> p(std::forward<F>(f));

    auto r=p.get_future(); // get the return value before we hand off the task
    {
      std::unique_lock<std::mutex> l(m);
      work.emplace_back(std::move(p)); // store the task<R()> as a task<void()>
    }
    v.notify_one(); // wake a thread to work on the task

    return r; // return the future result of the task
  }

  // start N threads in the thread pool.
  void start(std::size_t N=1){
    for (std::size_t i = 0; i < N; ++i)
    {
      // each thread is a std::async running this->thread_task():
      finished.push_back(
        std::async(
          std::launch::async,
          [this]{ thread_task(); }
        )
      );
    }
  }
  // abort() cancels all non-started tasks, and tells every working thread
  // stop running, and waits for them to finish up.
  void abort() {
    cancel_pending();
    finish();
  }
  // cancel_pending() merely cancels all non-started tasks:
  void cancel_pending() {
    std::unique_lock<std::mutex> l(m);
    work.clear();
  }
  // finish enques a "stop the thread" message for every thread, then waits for them:
  void finish() {
    {
      std::unique_lock<std::mutex> l(m);
      for(auto&&unused:finished){
        work.push_back({});
      }
    }
    v.notify_all();
    finished.clear();
  }
  ~tasks() {
    finish();
  }
private:
  // the work that a worker thread does:
  void thread_task() {
    while(true){
      // pop a task off the queue:
      std::packaged_task<void()> f;
      {
        // usual thread-safe queue code:
        std::unique_lock<std::mutex> l(m);
        if (work.empty()){
          v.wait(l,[&]{return !work.empty();});
        }
        f = std::move(work.front());
        work.pop_front();
      }
      // if the task is invalid, it means we are asked to abort:
      if (!f.valid()) return;
      // otherwise, run the task:
      f();
    }
  }
};

tasks.queue( []{ return "hello world"s; } ) 는 작업 객체가 실행될 때 hello world 로 채워지는 std::future<std::string> 반환합니다.

tasks.start(10) (10 개의 스레드를 시작 함 tasks.start(10) 를 실행하여 스레드를 작성합니다.

packaged_task<void()> 은 이동 전용 유형을 저장하는 유형 지우기 std::function 동등성이 없기 때문입니다. 그것들 중 하나를 작성하는 것은 아마도 packaged_task<void()> 사용하는 것보다 빠를 것이다.

실례 예 .

C ++ 11

C ++ 11에서는 result_of_t<blah>typename result_of<blah>::type .

뮤텍스 에 대한 자세한 내용.

스레드 로컬 저장소

스레드 로컬 저장소는 thread_local 키워드를 사용하여 만들 수 있습니다. thread_local 지정자로 선언 된 변수에는 스레드 저장 기간 이 있다고합니다 .

  • 프로그램의 각 스레드는 각 스레드 로컬 변수의 자체 복사본을 가지고 있습니다.
  • 함수 (로컬) 범위를 가진 스레드 로컬 변수는 처음 컨트롤이 정의를 통과 할 때 초기화됩니다. 이러한 변수는 extern 이라고 선언되지 않는 한 암시 적으로 정적입니다.
  • 네임 스페이스 또는 클래스 (로컬이 아닌) 범위를 가진 스레드 로컬 변수는 스레드 시작의 일부로 초기화됩니다.
  • 스레드 로컬 변수는 스레드 종료시 소멸됩니다.
  • 정적 멤버 인 경우 클래스의 멤버는 스레드 로컬 일 수 있습니다. 따라서 (스레드, 인스턴스) 쌍당 하나의 사본이 아니라 스레드 당 하나의 변수 사본이 있습니다.

예:

void debug_counter() {
    thread_local int count = 0;
    Logger::log("This function has been called %d times by this thread", ++count);
}


Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow