반응형

c++ 11로 넘어오면서 굉장히 강력한 기능이 생겼다.

바로 람다식과 functional을 조합하는 것이다.

이 기능을 사용하여 기존의 복잡한 과정을 걸쳐 생성했던 Job을 간단하게 생성할 수 있도록 코드를 수정해 볼것이다.

 

 

일반적인 함수포인터 같은 경우에는 인자를 저장할 공간이 없기 때문에

인자를 저장할 공간을 만들어서 거기에 저장해주고 있었다.

(전에 사용하던 Job 클래스에서 만든 functor안에 튜플로 함수 인자를 저장해주고 사용할 때 불러옴.)

 

하지만 람다에선 그런 과정없이 함수를 호출할 수 있다.

이런 장점이 있지만 사용할 때 주의해야 할 점 또한 있다.

 

1. 참조로 캡쳐할 경우.

std::function<void()> func = [&player]()
{
	GRoom->Enter(player);
};

 

이런 람다식을 생성하자 마자 호출하면 아무런 문제가 없지만, 우리가 Job을 사용하는 방식은 Job을 JobQueue에 집어 넣었다가, 나중에 누군가가 꺼내서 Job을 실행시켜준다.

이 때, 우리가 캡쳐해서 넣어준 값들이 유지가 되어야 함수가 실행될 수 있다.

 

예를 들어서 &player를 캡쳐해서 넘겨준다고 할 때,

player가 shared_ptr이라면 player의 레퍼런스 카운트는 증가하지 않고 그대로 유지될 것이다.

그래서 JobQueue에 넣은 Job(func)를 호출하기 전의 player의 레퍼런스 카운트가 0이 되어서 삭제되면 

유효하지 않은 주소에 접근하여 함수를 호출하려고 해서 오류가 나게 될 것이다.

이런 문제를 방지하기 위하여 레퍼런스 카운팅을 제대로 해주어 Job이 실행될 때 까지 캡쳐된 대상이 살아있다는 보장을 해주어야 한다.

 

2. 클래스 내부에서 this 포인터 캡쳐

class Knight
{
public:
	void Test()
	{
		auto job = [=]()
		{
			HealKnight(0, _hp);
		};
	}

	void HealKnight(int64 target, int32 value)
	{
		_hp += value;
		std::cout << target << "번 기사한테 힐" << value << "만큼 줌" << std::endl;
	}

private:
	int32 _hp = 100;
};

 

클래스 내에서 람다식을 사용하여 Job을 생성한다고 했을 때도 주의해야 할 점이 있다.

 

Test()라는 함수에서 job 람다식을 만들어 주고 있다.

하지만 위와 같이 캡처할 때 모든 대상을 복사하는 것은 매우 위험한 코드이다.

 

왜냐하면 위의 람다식을 풀어서 적으면 this포인터를 복사해서 넘기고 있기 때문이다.

job이 실행되기 위해서는 Knight 객체가 유효해야 하는데

모종의 이유로 Knight 객체가 삭제되었다면 job은 댕글리 포인터(삭제된 메모리)를 참조하게 되고 오류가 발생할 것 이다.

(그래서 람다를 사용할 때는 [=]같이 모든 대상을 복사하는 것보다 [this] 처럼 필요한 대상을 명확하게 캡쳐하는게 버그 찾기에 더 수월하다.)

 

이를 방지하기 위해 shared_ptr를 사용하여 레퍼런스 카운팅을 해주어 생명 주기를 관리한다면,

shared_ptr와 this 포인터는 함께 사용할 수 없기 때문에, 아래와 같이 코드를 수정하면 된다.

 

class Knight : public enable_shared_from_this<Knight>
{
...
		auto job = [self = shared_from_this()]()
		{
			self->HealKnight(0, self->_hp);
		};
...
};

 


 

이제 원리를 알았으니 Job 클래스에서 스마트 포인터를 사용하여 생명주기를 안전하게 관리해주는 기능을 래핑해주자.

 

Job.h

#pragma once
#include <functional>

/*==================
		Job
===================*/

using CallbackType = std::function<void()>;

class Job
{
public:
	Job(CallbackType&& callback) : _callback(std::move(callback)) {}

	//보통 멤버함수를 많이 사용하기 때문에 멤버함수 사용하는 버전으로 작성
	//이렇게 사용할 경우 알아서 레퍼런스 카운팅이 증가됨.
	template<typename T, typename Ret, typename ... Args>
	Job(shared_ptr<T> owner, Ret(T::* memFunc)(Args...), Args&&... args)
	{
		_callback = [owner, memFunc, args...]()
		{
			(owner.get()->*memFunc)(args...);
		};
	}

	void Execute()
	{
		_callback();
	}

private:
	CallbackType _callback;
};

 

 

원래 우리는 Room에서 job을 push하고 pop해주었는데 이 기능들을 JobSerializer 클래스로 옮겨주자.

그리고 JobQueue를 사용하는 클래스에서 JobSerializer를 상속받게 하여 관리할 것이다.

 

/*==================
* 
*	JobSerializer
* 
==================*/
class JobSerializer : public enable_shared_from_this<JobSerializer>
{
public:
	//일반 람다식
	void PushJob(CallbackType&& callback)
	{
		auto job = ObjectPool<Job>::MakeShared(std::move(callback));
		_jobQueue.Push(job);
	}

	//함수 지정, 인자 따로 지정
	template<typename T, typename Ret, typename... Args>
	void PushJob(Ret(T::*memFunc)(Args...),Args... args)
	{
		shared_ptr<T> owner = static_pointer_cast<T>(shared_from_this());
		auto job = ObjectPool<Job>::MakeShared(owner, memFunc,std::forward<Args>(args)...);
		_jobQueue.Push(job);
	}

	virtual void FlushJob() abstract;

protected:
	JobQueue _jobQueue;
};

 

#pragma once
#include "JobSerializer.h"

class Room : public JobSerializer
{
public:
	void Enter(PlayerRef player);
	void Leave(PlayerRef player);
	void Broadcast(SendBufferRef sendBuffer);

public:
	virtual void FlushJob() override;

private:
	//JobQueue _jobs;
	map<uint64, PlayerRef> _players;
};

extern shared_ptr<Room> GRoom;

 

 

이제 수정된 위의 코드들을 다른 코드에 적용만 시키면 끝이다.

 

bool Handle_C_ENTER_GAME(PacketSessionRef& session, Protocol::C_ENTER_GAME& pkt)
{
	...
	GRoom->PushJob(&Room::Enter, player);
	...
}


bool Handle_C_CHAT(PacketSessionRef& session, Protocol::C_CHAT& pkt)
{
	...
	GRoom->PushJob(&Room::Broadcast, sendBuffer);
    ...
}

 

 

 

이렇게 해줌으로서 Job을 람다식으로 간단하게 생성하여 JobQueue에 넣어주고 생명주기도 관리가 되도록 하였다.

 

 

반응형

'네트워크 프로그래밍' 카테고리의 다른 글

JobQueue(5)  (0) 2024.01.17
JobQueue(4)  (0) 2024.01.05
JobQueue (2)  (0) 2023.12.29
Job Queue (1)  (0) 2023.10.03
에코 채팅 프로그램  (0) 2023.10.02