C++11의 표준 동시성 라이브러리에 대한 이해

개요

이번 주제는 C++ 코드 실행의 Synchronous(동기) vs Asynchronous(비동기) 에 대한 기록입니다.

동기화된 프로그래밍은 어떤 한 작업이 완료될 때까지 다른작업은 수행되지 않고, 비동기는 그 반대입니다.

 

Sync, Async

아래 코드를 살펴보겠습니다. 

수행시간이 1초 걸리는 함수를 3번 호출 합니다.

#include <iostream>
#include <thread>
#include <chrono>

void do_something(int n)
{
    std::cout << "Sleep for " << n << " seconds\n";
    std::this_thread::sleep_for(std::chrono::seconds(n));
}

int main()
{
    do_something(1);
    do_something(1);
    do_something(1);
}

결과는 동기적으로 함수가 수행되어 총 3초가 걸렸습니다.

 

아마도 C++을 공부해 본 사람이라면 동시에 수행하기 위해 Thread를 떠올릴 것입니다.

#include <iostream>
#include <thread>
#include <chrono>

void do_something(int n)
{    
    std::cout << "Sleep for " << n << " seconds\n";   
    std::this_thread::sleep_for(std::chrono::seconds(n));    
}

int main()
{
    auto t1 = std::thread(do_something, 1);
    auto t2 = std::thread(do_something, 1);
    auto t3 = std::thread(do_something, 1);

    t1.join();
    t2.join();
    t3.join();
}

3개의 실행흐름(Thread)가 비동기적으로 수행되어 결과는 1초가 걸렸습니다.


그런데 결과출력이 좀 이상합니다.

Thread 동기화를 수행하지 않았네요.

Race condition에 들어간 thread들을 Mutex를 이용해 동기화 합니다.

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

std::mutex m;

void do_something(int n)
{
    m.lock();
    std::cout << "Sleep for " << n << " seconds\n";
    m.unlock();
    std::this_thread::sleep_for(std::chrono::seconds(n));    
}

int main()
{
    auto t1 = std::thread(do_something, 1);
    auto t2 = std::thread(do_something, 1);
    auto t3 = std::thread(do_something, 1);

    t1.join();
    t2.join();
    t3.join();
}

이제 cout구문이 동기화되어 정상적으로 출력됩니다.

당연하게도 비동기 방식으로 진행하는 것이 효율이 좋습니다.

 


Concurrency support library

저는 위와 같이 비동기적 처리가 필요한 경우 Thread를 활용해 코드를 작성해왔었습니다.

그런데 리턴타입이 있는 경우는 Thread를 통한 리턴값 회수가 쉽지 않았습니다. 또한 Thread의 수가 지나치게 많아지는 경우와 효율성을 위한 Threadpool을 작성하는데 드는 수고가 많았습니다.

자료를 찾아 공부하다보니 아래와 같은 C++ 11표준 동시성 라이브러리가 존재합니다.

#include <iostream>
#include <thread>
#include <chrono>
#include <future>

int do_something(int n)
{
    std::cout << "Sleep for " << n << " seconds\n";
    std::this_thread::sleep_for(std::chrono::seconds(n));
    return n;
}

int main()
{
    auto a1 = std::async(std::launch::async, do_something, 1);
    auto a2 = std::async(std::launch::deferred, do_something, 1);
    auto a3 = std::async([]() {return do_something(1);});

    std::cout << a1.get() + a2.get() + a3.get() << '\n';
}

std::async 함수 템플릿은 C++에서 비동기 프로그래밍을 위한 라이브러리입니다.

비동기를 구현하기 위해 별도의 스레드에서 함수를 실행하고 스레드 관리의 세부 사항에 대해 걱정할 필요가 없습니다. (join, detach 등)

위 예제에서 std::asyncdo_something함수를 비동기 방식으로 실행합니다. 실행되는 함수는 별도의 쓰레드에서 실행될 수 있으며, 이 쓰레드는 ThreadPool의 일부일 수도 있고 아닐수도 있다고 표현되어 있습니다.

(내부적으로 스레드를 생성 후 함수실행, ThreadPool 은 ? )

ThreadPool 생성 여부는 컴파일러마다 다른건지 OS마다 다른건지는 잘 모르겠습니다.

[cpprefrernce 인용]

The function template std::async runs the function f asynchronously (potentially in a separate thread which might be a part of a thread pool) and returns a std::future that will eventually hold the result of that function call.

 

std::async 함수는 함수 객체와 런칭 정책을 인수로 취하는데 그 의미는 아래와 같습니다.

  • std::launch : 전달 함수를 별도의 쓰레드를 생성해 즉시 실행.

  • std::deffred : 전달 함수를 별도의 쓰레드를 생성하지 않고 실행. (바로 실행, X, get함수 호출시 실행)

 

vcpkg를 다루다 보면 Task기반의 비동기 예제들을 많이 보게되는데, 최근 잘 이해되지 않던 부분을 공부하며 기록으로 남겨두고자 작성한 글 입니다.

이상으로 설명을 마칩니다.


참고자료

1. https://en.cppreference.com/w/cpp/thread/async


감사합니다.

댓글

이 블로그의 인기 게시물

Qt Designer 설치하기

PyQt5 기반 동영상 플레이어앱 만들기