future & promise 사용법


  • promise 객체를 통해 자신과 연결된 future 객체를 생성할 수 있습니다. promise 객체에 값이 셋팅되었을 때 future를 통해서 해당 값을 가져올 수 있습니다.




future & promise 사용법 예시


#include <iostream>
#include <future>

using namespace std;

void WorkerThread(promise<string>* p, const string *name)
{
    try
    {
        // name이 nullptr일 경우 throw
        if (name == nullptr)
        {
            throw runtime_error("name is nullptr");
        }

        // promise에 값을 셋팅
        p->set_value(*name + ", Hello\n");
    }
    catch (const std::exception& exp)
    {
        // promise에 exception_ptr을 셋팅
        p->set_exception(make_exception_ptr(exp));
    }

    return;
}

int main()
{
    promise<string> p;

    // 자신과 연결된 promise 객체를 생성
    future<string> f = p.get_future();

    string str = "devhun";

    thread t(WorkerThread, &p, nullptr);

    f.wait();

    try
    {
        // promise에 셋팅된 값을 가져옴
        std::cout << f.get();
    }
    catch (const std::exception& exp)
    {
        // promise에 셋팅된 exception 값을 가져옴
        std::cout << exp.what();
    }

    t.join();

    return 0;
}

  • future와 promise를 통해서 다른 쓰레드에서의 결과값을 가져올 수 있습니다. 그리고 future의 get은 단 한번만 사용할 수 있습니다.

  • future를 통해서 exception 값을 셋팅하고 promise를 통해서 exception 값을 가져올 수 있습니다. 이때 exception을 promise에 셋팅할 때 반드시 exception_ptr로 셋팅해야 합니다. 그리고 promise에 exception이 셋팅되면 future의 get에서 이를 catch할 수 있습니다.

  • future의 get만을 사용해서도 promise에 값이 셋팅될 때까지 기다리지만, future의 get보다 wait이 가볍기 때문에 wait을 이용해서 대기하고 get을 통해 값을 가져오는 것이 좋습니다.




shared_future


#include <iostream>
#include <future>

using namespace std;

void WorkerThread(promise<string>* p, const string *name)
{
    try
    {
        if (name == nullptr)
        {
            throw runtime_error("name is nullptr");
        }

        p->set_value(*name + ", Hello\n");
    }
    catch (const std::exception& exp)
    {
        p->set_exception(make_exception_ptr(exp));
    }

    return;
}

int main()
{
    promise<string> p;

    shared_future<string> f = p.get_future();

    string str = "devhun";

    thread t(WorkerThread, &p, &str);

    f.wait();

    try
    {
        for (int i = 0; i < 10; ++i)
        {
            std::cout << f.get();
        }
    }
    catch (const std::exception& exp)
    {
        std::cout << exp.what() << "\n";
    }

    t.join();

    return 0;
}

  • future는 get 함수를 한 번만 호출할 수 있으며, 이후 호출부터는 no state 예외가 발생됩니다. 하지만, shared_future를 사용할 경우 get을 중복하여 호출할 수 있습니다.




packaged_task


#include <iostream>
#include <future>

using namespace std;

string WorkerThread(const string *name, const string* hello)
{
    this_thread::sleep_for(chrono::seconds(2));

    return *name + *hello;
}

int main()
{
    // return 타입과 매개변수 타입을 지정
    packaged_task<string(const string*, const string*)> task(WorkerThread);

    future<string> f = task.get_future();

    string str1 = "devhun";
    string str2 = ", Hello";

    // 복사 생성이 불가능하기 때문에 move를 통해 인자로 전달해야 합니다.
    thread t(move(task), &str1, &str2);

    cout << f.get() << endl;

    t.join();

    return 0;
}

  • packaged_task를 생성할 때 비동기로 실행할 함수를 매개 변수로 전달하고 함수의 리턴 타입과 매개 변수 타입에 대한 템플릿 타입을 지정합니다. 그리고 생성된 packaged_task를 이용해서 future 객체를 생성하여 이를 통해 비동기 실행에 대한 결과를 받아볼 수 있습니다.

  • packaged_task는 promise의 set_value를 사용하지 않고 함수의 return 타입을 대신하여 처리할 수 있습니다.

  • packaged_task는 promise의 set_exception을 사용하지 않고도 task에 대한 예외가 설정됩니다.




async


#include <iostream>
#include <future>

using namespace std;

string WorkerThread(const string *name, const string* hello)
{
    this_thread::sleep_for(chrono::seconds(2));

    return *name + *hello;
}

int main()
{
    string str1 = "devhun";
    string str2 = ", Hello";

    future<string> f = async(launch::async, WorkerThread, &str1, &str2);

    cout << f.get() << endl;

    return 0;
}

  • promise 또는 packaged_task는 비동기적으로 실행하기 위해서 thread 객체를 직접 생성하여 처리해야 했습니다. 하지만, asyn를 사용할 경우 내부적으로 쓰레드를 생성하여 매개변수로 전달한 함수를 비동기적으로 처리합니다.




lanch::async


async(launch::async, WorkerThread, &str1, &str2);

  • launch::async를 전달할 경우 내부적으로 새로운 쓰레드를 생성하여 함수를 비동기적으로 처리합니다.




lanch::deferred


async(launch::deferred, WorkerThread, &str1, &str2);

  • launch::deferred를 사용할 경우 별도의 쓰레드를 생성하지 않고 future 객체를 통해 get을 호출하였을 때 해당 쓰레드에서 함수를 호출하는 방식입니다.




'Programming Language > C, C++' 카테고리의 다른 글

C++ condition_variable 사용 방법  (0) 2023.04.15
C++ unique_lock 사용방법  (0) 2023.04.15
C++ mutex와 lock_guard  (0) 2023.04.15
C++ 다중 상속(Multiple inheritance)이란?  (0) 2023.03.26
C++ 람다(lambda)란?  (0) 2023.03.25

condition_variable

 

  • condition_variable은 C++11에 추가된 클래스로써, condition_variable을 통해 생성한 객체를 이용하면 조건에 맞지 않으면 대기하고, 다른 쓰레드에서 조건이 맞게 수정하였다면 이를 알려 대기중인 쓰레드를 깨울 수 있습니다.


  • condition_variable은 Event 객체와 같이 Windows에 종속적인 커널 오브젝트를 직접적으로 사용하지 않고 여러 OS에서 공통적으로 사용할 수 있는 클래스입니다.

 

  • condition_variable은 unique_lock을 같이 병행하여 사용하여 조건에 해당하는 값을 읽고 수정할 수 있습니다.




condition_variable 사용방법

 

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

std::mutex m;
std::condition_variable cv;
std::queue<int> q;

void EnqueueThread()
{
    for(;;)
    {
        {
            std::unique_lock<std::mutex> lk(m);
            q.push(rand() % 10);
        }

        // 동일한 condition_variable 객체를 대상으로 대기중인 쓰레드를 깨워 조건을 확인시킴
        cv.notify_one();
    }

    return;
}

void DequeueThread()
{
    for (;;)
    {
        std::unique_lock<std::mutex> lk(m);

        // lock을 건 상태에서 조건을 확인
        // 조건에 맞지 않는다면 lock을 해제 후 쓰레드 block
        // 조건에 맞을 경우 lock을 유지한 상태에서 쓰레드 block을 하지 않고 다음 로직 수행
        cv.wait(lk, []()->bool {return !q.empty(); });

        std::cout << q.front() << "\n";
        q.pop();
    }

    return;
}

int main()
{
    std::thread th1(EnqueueThread);
    std::thread th2(DequeueThread);

    th1.join();
    th2.join();

    return 0;
}

 

  • wait에 unique_lock과 람다를 전달해서 조건에 조건에 맞을 경우 unique_lock에 대한 소유권을 유지한체 다음 로직을 수행하고, 조건에 맞지 않을 경우 unique_lock에 대한 소유권을 해제하고 쓰레드 블락됩니다.

 

  • notify_one은 동일한 condition_variable 객체를 대상으로 대기중인 쓰레드를 하나를 깨워 condition을 다시 한 번 확인하도록 요청합니다. condition이 맞을 경우 이후 다음 로직을 수행하고, condition이 맞지 않을 경우 다음 로직을 수행하지 않고 다시 block됩니다.




'Programming Language > C, C++' 카테고리의 다른 글

future & promise, packaged_task, async 사용법  (0) 2023.04.18
C++ unique_lock 사용방법  (0) 2023.04.15
C++ mutex와 lock_guard  (0) 2023.04.15
C++ 다중 상속(Multiple inheritance)이란?  (0) 2023.03.26
C++ 람다(lambda)란?  (0) 2023.03.25

unique_lock 이란?

 

  • unique_lock 객체는 lock_guard와 같이 RAII 기법을 통해 생성자에서 락에 대한 소유권을 획득하고 소멸자에서 락에 대한 소유권을 반환하는 객체입니다.

 

  • unique_lock을 생성할 때 derfer_lock, try_to_lock, adopt_lock 중 하나를 추가로 전달하여 lock_guard보다 다양한 기능을 사용할 수 있습니다.

 

  • lock_guard가 상대적으로 unique_lock 보다 더 가볍기 때문에 lock_guard를 우선적으로 사용하고 필요시에 unique_lock을 사용하는 것이 좋습니다.




defer_lcok

 

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

int num;
std::mutex m;

void WorkerThread() {
    for (int i = 0; i < 100000; ++i)
    {
        std::unique_lock<std::mutex> lk(m, std::defer_lock);
        lk.lock();

        ++num;
    }

    return;
}


int main()
{
    std::thread th1(WorkerThread);
    std::thread th2(WorkerThread);

    th1.join();
    th2.join();

    std::cout << num;

    return 0;
}

 

  • defer_lock을 전달할 경우 소유권 획득 시점을 생성자가 아닌 lock 멤버 함수를 호출하는 시점으로 연기할 수 있습니다. 그리고 lock이 호출되었다면, 소멸자에서 이를 반환합니다.




try_to_lock

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

int num;
std::mutex m;

void WorkerThread() {
    for (int i = 0; i < 100000; ++i)
    {
        std::unique_lock<std::mutex> lk(m, std::try_to_lock);
        if (!lk.owns_lock()) {
            --i;
            continue;
        }

        ++num;
    }

    return;
}


int main()
{
    std::thread th1(WorkerThread);
    std::thread th2(WorkerThread);

    th1.join();
    th2.join();

    std::cout << num;

    return 0;
}

 

  • try_to_lock은 생성자에서 lock에 대한 소유권을 획득할 수 있는지 여부를 시도를 합니다. 만약, 생성자에서 lock에 대한 소유권을 획득하였다면, owns_lock() 멤버 함수의 return 값이 true이고 획득하지 못했다면 false를 return 합니다.




adopt_lock

 

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

int num;
std::mutex m;

void WorkerThread() {
    for (int i = 0; i < 100000; ++i)
    {
        m.lock();
        std::unique_lock<std::mutex> lk(m, std::adopt_lock);
        ++num;
    }

    return;
}


int main()
{
    std::thread th1(WorkerThread);
    std::thread th2(WorkerThread);

    th1.join();
    th2.join();

    std::cout << num;

    return 0;
}

 

  • adopt_lock는 이미 매개변수로 전달한 동기화 객체에 대한 소유권이 이미 획득되어 있는 상태이고, 이를 소멸자에서 반환하기 위해서 사용합니다.




+ Recent posts