본문 바로가기
C++20/4th Emperor

[C++20 - 4th Emperor] Chapter 04. Coroutine

by song.ift 2023. 7. 25.

GitHub : https://github.com/developeSHG/CPP20-4th_Emperor/commit/19ff2673e96a446bee711c99873b180d69b52391

 

Coroutine · developeSHG/CPP20-4th_Emperor@19ff267

developeSHG committed Jul 26, 2023

github.com

 

 


 

 

Module 기능과 함께 가장 중요한 기능인 Coroutine

 

// 유니티 = 코루틴
// - C#에서 있는 코루틴 문법을 유니티에서 적극적으로 채용해서 제공 

어떤 함수를 호출할 때, 함수를 어디까지 진행했는지 저장하고, 일시정지했다가 다시 이어서 호출할 수 있는 기능

간단히 말해서 내가 원할 때, 핸들을 이용해 코드를 다시 재게하거나 제거해버리는 동작을 하는 게 코루틴 문법.

 

// 몬스터 잡으면 -> 아이템이 인벤토리에 들어온다.

// 1. 몬스터를 잡음

// 2. DB처리와 아이템 생성 동시에 적용 (DB 처리시간 단축)

void KillMonster()

{

  // DB에 아이템 생성 요청

 

  // -- DB 처리 -- (DB 처리는 굉장히 오래걸림)

 

  // 아이템 생성

  // 인벤토리에 아이템 추가

}

이렇게 되었을 때, 만약 DB처리가 실패했을 경우, DB세계와 실제세계가 달라서 아이템 복사가 발생하는 등 메모리가 불일치한 큰 문제로 발생할 수 있다.

 

그래서 다시 

// 몬스터 잡으면 -> 아이템이 인벤토리에 들어온다.

// 1. 몬스터를 잡음

// 2. DB 먼저 적용

// 3. 아이템 생성 + 인벤토리에 추가

 

하지만 DB처리 시간이 오래걸린다는 단점이 있었다.

그래서 시점을 저장했다가, 복원해서 다시 재생시킬 수 있는 기능인 코루틴을 사용

 

밑에 코드 예제가 있지만, 사실 좀 직접 서브 프로젝트에 사용한다고 하면 복잡하고, 문법이 지저분한 편인 것 같아서 사용하기가 좀 꺼려진다.

C#과 다르게 C++에서 코루틴이란 거 자체는 문법이 굉장히 복잡하지만, 사실상 단순한 문법이 아니라 일종의 프레임워크를 제공해준셈 같다. 사용하기 위해선 틀에 맞춘 함수를 사용해야 기능이 된다. 

 


 

- Coroutine.cpp

#include <iostream>
using namespace std;
#include <list>
#include <vector>
#include <map>
#include <algorithm>
#include <ranges>
#include <concepts>
#include <coroutine>
#include "MyCoroutine.h"
// 오늘의 주제 : Coroutine

// 유니티 = 코루틴
// - C#에서 있는 코루틴 문법을 유니티에서 적극적으로 채용해서 제공 

struct CoroutineObject
{
	// 1) promise 객체는 다음과 같은 인터페이스를 제공해야 함 (코드에 따라서 그에 대응하는 프레임워크 함수를 정의해야 사용가능)
	// - 기본 생성자 : promise 객체는 기본 생성자로 만들어질 수 있어야 함
	// - get_return_object : 코루틴 객체를 반환 (resumable object)
	// - return_value(val) : co_return val에 의해 호출됨 (코루틴이 영구적으로 끝나지 않으면 없어도 됨)
	// - return_void() : co_return에 의해 호출됨 (코루틴이 영구적으로 끝나지 않으면 없어도 됨)
	// - yield_value(val) : co_yield에 의해 호출됨
	// - initial_suspend() : 코루틴이 실행 전에 중단/연기될 수 있는지
	// - final_suspend() : 코루틴이 종료 전에 중단/연기될 수 있는지
	// - unhandled_exception() : 예외 처리시 호출됨
	struct promise_type
	{
		CoroutineObject get_return_object() { return {}; }
		std::suspend_never initial_suspend() const noexcept { return {}; }
		std::suspend_never final_suspend() const noexcept { return {}; }
		void return_void() { }
		void unhandled_exception() { }
	};
};

CoroutineObject HelloCoroutine()
{
	co_return;
}

/*{
		Promise prom; (get_return_object)
		co_await prom.initial_suspend();
		try
		{
			// co_return, co_yield, co_await를 포함하는 코드
		}
		catch (...)
		{
			prom.unhandled_exception();
		}
		co_await prom.final_suspend();
}*/
Future<int> CreateFuture()
{
	co_return 2021;
}

Generator<int> GenNumbers(int start = 0, int delta = 1)
{
	int now = start;

	while (true)
	{
		co_yield now; // yield return
		now += delta;
	}
}

Job PrepareJob()
{
	// co_await [Awaitable]
	co_await std::suspend_never();
}

int main()
{
	// 함수가 코루틴이 되려면...
	// - co_return
	// - co_yield
	// - co_await

	// 코루틴을 사용할 수 있는 Framework를 제공한다 (...)
	// 3가지 요소로 구성
	// - promise 객체
	// - 코루틴 핸들 (밖에서 코루틴을 resume / destroy 할 때 사용. 일종의 리모컨)
	// - 코루틴 프레임 (promise 객체, 코루틴이 인자 등을 포함하는 heap 할당 객체) (함수를 호출할 때, 스택 프레임이 생성되는 것처럼 코루틴을 실행할 때도 코루틴 프레임이란 게 만들어져서 현재 상태를 보존해야하니까 heap에 할당됨)

	// co_yield, co_await, co_return을 함수 안에서 사용하면, 그 함수는 코루틴이 됨.
	/*{
		Promise prom; (get_return_object)
		co_await prom.initial_suspend();
		try
		{
			// co_return, co_yield, co_await를 포함하는 코드
		}
		catch (...)
		{
			prom.unhandled_exception();
		}
		co_await prom.final_suspend();
	}*/

	// co_await 플로우
	/*{
		if awaitable.await_ready() returns false;
			suspend coroutine
			awaitable.await_suspend(handle) returns:
				void:
					awaitable.await_suspend(handle);
					coroutine keeps suspended
					return to caller
				bool:
					bool result = awaitable.await_suspend(handle);
					if (result)
						coroutine keeps suspended
						return to caller
					else
						return awaitable.await_resume()
				another coroutine handle:
					anotherCoroutineHandle = awaitable.await_suspend(handle);
					anotherCoroutineHandle.resume();
					return to caller;
		return awaitable.await_resume();
	}*/

	auto future = CreateFuture();
	// TODO : 다른걸 하다...
	cout << future.get() << endl;

	auto numbers = GenNumbers(0, 1);

	for (int i = 0; i < 20; i++)
	{
		numbers.next();

		cout << " " << numbers.get();
	}

	auto job = PrepareJob();

	job.start();
}

 

- MyCoroutine.h

#pragma once
#include <coroutine>
#include <iostream>
using namespace std;

// 코루틴 객체
template<typename T>
class Future
{
public:
	Future(shared_ptr<T> value) : _value(value) { }
	T get() { return *_value; }

private:
	shared_ptr<T> _value;

public:
	struct promise_type
	{
		Future<T> get_return_object() { return Future<T>(_ptr); }
		void return_value(T value) { *_ptr = value; }
		std::suspend_never initial_suspend() { return {}; }
		std::suspend_never final_suspend() noexcept { return {}; }
		void unhandled_exception() { }

		// 데이터
		shared_ptr<T> _ptr = make_shared<T>();
	};
};

template<typename T>
class Generator
{
public:
	struct promise_type;
	using handle_type = coroutine_handle<promise_type>;

	Generator(handle_type handle) : _handle(handle)
	{
	}

	~Generator()
	{
		if (_handle)
			_handle.destroy();
	}

	T get() { return _handle.promise()._value; }

	bool next()
	{
		_handle.resume(); // 중요!
		return !_handle.done();
	}

private:
	handle_type _handle; // 코루틴 핸들로 코루틴을 재게할지 삭제할지 등 관리해주는 용도

public:
	struct promise_type
	{
		Generator<T> get_return_object() { return Generator(handle_type::from_promise(*this)); }
		std::suspend_always initial_suspend() { return {}; }
		std::suspend_always final_suspend() noexcept { return {}; }
		std::suspend_always yield_value(const T value) { _value = value; return {}; }
		std::suspend_always return_void() { return {}; }
		void unhandled_exception() { }

		T _value;
	};
};


class Job
{
public:
	struct promise_type;
	using handle_type = coroutine_handle<promise_type>;

	Job(handle_type handle) : _handle(handle)
	{
	}

	~Job()
	{
		if (_handle)
			_handle.destroy();
	}

	void start()
	{
		if (_handle)
			_handle.resume();
	}

private:
	handle_type _handle;

public:
	struct promise_type
	{
		Job get_return_object() { return Job(handle_type::from_promise(*this)); }
		std::suspend_always initial_suspend() { cout << "Prepare Job" << endl;	return {}; }
		std::suspend_never final_suspend() noexcept { cout << "Do Job" << endl; return {}; }
		std::suspend_never return_void() { return {}; }
		void unhandled_exception() { }
	};
};

 

'C++20 > 4th Emperor' 카테고리의 다른 글

[C++20 - 4th Emperor] Chapter 03. Range  (0) 2023.07.25
[C++20 - 4th Emperor] Chapter 02. Module  (0) 2023.07.25
[C++20 - 4th Emperor] Chapter 01. Concept  (0) 2023.07.24

댓글